diff --git a/.gitignore b/.gitignore index 346a56ca..6bbcdb2e 100644 --- a/.gitignore +++ b/.gitignore @@ -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 \ No newline at end of file +/custom_config.yml diff --git a/README.md b/README.md index 98e4b50c..94655b90 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/source/_static/example_scenes/InteractiveDevlopment.mp4 b/docs/source/_static/example_scenes/InteractiveDevelopment.mp4 similarity index 100% rename from docs/source/_static/example_scenes/InteractiveDevlopment.mp4 rename to docs/source/_static/example_scenes/InteractiveDevelopment.mp4 diff --git a/docs/source/development/changelog.rst b/docs/source/development/changelog.rst index 1460d9b6..573df074 100644 --- a/docs/source/development/changelog.rst +++ b/docs/source/development/changelog.rst @@ -1,4 +1,44 @@ Changelog ========= -No changes now. \ No newline at end of file +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`` \ No newline at end of file diff --git a/docs/source/documentation/custom_config.rst b/docs/source/documentation/custom_config.rst index 496c87a9..86f4eb8d 100644 --- a/docs/source/documentation/custom_config.rst +++ b/docs/source/documentation/custom_config.rst @@ -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`` ----------------------------- diff --git a/docs/source/getting_started/configuration.rst b/docs/source/getting_started/configuration.rst index 17319bb1..eb981b19 100644 --- a/docs/source/getting_started/configuration.rst +++ b/docs/source/getting_started/configuration.rst @@ -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 ``, -it will overwrite ``manim/custom_config.yml`` with ``custom_config.yml`` -in the ``project`` folder. \ No newline at end of file +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 \ No newline at end of file diff --git a/docs/source/getting_started/example_scenes.rst b/docs/source/getting_started/example_scenes.rst index 4a9aa4f1..203c84e7 100644 --- a/docs/source/getting_started/example_scenes.rst +++ b/docs/source/getting_started/example_scenes.rst @@ -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() diff --git a/docs/source/getting_started/quickstart.rst b/docs/source/getting_started/quickstart.rst index aa68950f..0151b6fd 100644 --- a/docs/source/getting_started/quickstart.rst +++ b/docs/source/getting_started/quickstart.rst @@ -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 diff --git a/docs/source/getting_started/structure.rst b/docs/source/getting_started/structure.rst index f1a05623..6c5f3375 100644 --- a/docs/source/getting_started/structure.rst +++ b/docs/source/getting_started/structure.rst @@ -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 diff --git a/example_scenes.py b/example_scenes.py index 10484451..6931ff0b 100644 --- a/example_scenes.py +++ b/example_scenes.py @@ -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 diff --git a/manimlib/__main__.py b/manimlib/__main__.py index ebd342fe..37264645 100644 --- a/manimlib/__main__.py +++ b/manimlib/__main__.py @@ -15,3 +15,6 @@ def main(): for scene in scenes: scene.run() + +if __name__ == '__main__': + main() diff --git a/manimlib/config.py b/manimlib/config.py index 5c0e88eb..e307ac6e 100644 --- a/manimlib/config.py +++ b/manimlib/config.py @@ -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]) diff --git a/manimlib/mobject/geometry.py b/manimlib/mobject/geometry.py index f8f523b8..69511d67 100644 --- a/manimlib/mobject/geometry.py +++ b/manimlib/mobject/geometry.py @@ -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): diff --git a/manimlib/mobject/svg/tex_mobject.py b/manimlib/mobject/svg/tex_mobject.py index 2cd8a648..0680645f 100644 --- a/manimlib/mobject/svg/tex_mobject.py +++ b/manimlib/mobject/svg/tex_mobject.py @@ -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): diff --git a/manimlib/mobject/vector_field.py b/manimlib/mobject/vector_field.py index 0776a3cc..b7f69096 100644 --- a/manimlib/mobject/vector_field.py +++ b/manimlib/mobject/vector_field.py @@ -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 diff --git a/manimlib/shaders/inserts/finalize_color.glsl b/manimlib/shaders/inserts/finalize_color.glsl index 0664deb0..e7b64eee 100644 --- a/manimlib/shaders/inserts/finalize_color.glsl +++ b/manimlib/shaders/inserts/finalize_color.glsl @@ -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)); diff --git a/manimlib/shaders/surface/frag.glsl b/manimlib/shaders/surface/frag.glsl index db905275..707621a1 100644 --- a/manimlib/shaders/surface/frag.glsl +++ b/manimlib/shaders/surface/frag.glsl @@ -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; diff --git a/manimlib/shaders/textured_surface/frag.glsl b/manimlib/shaders/textured_surface/frag.glsl index ab45dad6..616b06e2 100644 --- a/manimlib/shaders/textured_surface/frag.glsl +++ b/manimlib/shaders/textured_surface/frag.glsl @@ -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; diff --git a/manimlib/shaders/true_dot/frag.glsl b/manimlib/shaders/true_dot/frag.glsl index a8965359..0be9a2ab 100644 --- a/manimlib/shaders/true_dot/frag.glsl +++ b/manimlib/shaders/true_dot/frag.glsl @@ -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; diff --git a/manimlib/utils/init_config.py b/manimlib/utils/init_config.py index 064db879..f126849f 100644 --- a/manimlib/utils/init_config.py +++ b/manimlib/utils/init_config.py @@ -23,6 +23,7 @@ def init_customization(): "background_color": "", }, "window_position": "UR", + "window_position": 0, "break_into_partial_movies": False, "camera_qualities": { "low": { diff --git a/manimlib/window.py b/manimlib/window.py index b6dc40db..033405d3 100644 --- a/manimlib/window.py +++ b/manimlib/window.py @@ -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 diff --git a/requirements.txt b/requirements.txt index c5f049a0..a265599b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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