The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .github
    ├── ISSUE_TEMPLATE
    │   ├── bug_report.md
    │   ├── config.yml
    │   └── error-when-using.md
    ├── PULL_REQUEST_TEMPLATE.md
    └── workflows
    │   ├── docs.yml
    │   └── publish.yml
├── .gitignore
├── LICENSE.md
├── MANIFEST.in
├── README.md
├── docs
    ├── Makefile
    ├── example.py
    ├── make.bat
    ├── requirements.txt
    └── source
    │   ├── _static
    │       └── icon.png
    │   ├── conf.py
    │   ├── development
    │       ├── about.rst
    │       ├── changelog.rst
    │       └── contributing.rst
    │   ├── documentation
    │       ├── animation
    │       │   └── index.rst
    │       ├── camera
    │       │   └── index.rst
    │       ├── constants.rst
    │       ├── custom_config.rst
    │       ├── mobject
    │       │   └── index.rst
    │       ├── scene
    │       │   └── index.rst
    │       ├── shaders
    │       │   └── index.rst
    │       └── utils
    │       │   └── index.rst
    │   ├── getting_started
    │       ├── configuration.rst
    │       ├── example_scenes.rst
    │       ├── installation.rst
    │       ├── quickstart.rst
    │       ├── structure.rst
    │       └── whatsnew.rst
    │   ├── index.rst
    │   └── manim_example_ext.py
├── example_scenes.py
├── logo
    ├── cropped.png
    ├── graph.png
    ├── logo.py
    ├── transparent_graph.png
    ├── white_with_name.png
    ├── with_name.png
    └── with_subtext.png
├── manimlib
    ├── __init__.py
    ├── __main__.py
    ├── animation
    │   ├── __init__.py
    │   ├── animation.py
    │   ├── composition.py
    │   ├── creation.py
    │   ├── fading.py
    │   ├── growing.py
    │   ├── indication.py
    │   ├── movement.py
    │   ├── numbers.py
    │   ├── rotation.py
    │   ├── specialized.py
    │   ├── transform.py
    │   ├── transform_matching_parts.py
    │   └── update.py
    ├── camera
    │   ├── __init__.py
    │   ├── camera.py
    │   └── camera_frame.py
    ├── config.py
    ├── constants.py
    ├── default_config.yml
    ├── event_handler
    │   ├── __init__.py
    │   ├── event_dispatcher.py
    │   ├── event_listner.py
    │   └── event_type.py
    ├── extract_scene.py
    ├── logger.py
    ├── mobject
    │   ├── __init__.py
    │   ├── boolean_ops.py
    │   ├── changing.py
    │   ├── coordinate_systems.py
    │   ├── frame.py
    │   ├── functions.py
    │   ├── geometry.py
    │   ├── interactive.py
    │   ├── matrix.py
    │   ├── mobject.py
    │   ├── mobject_update_utils.py
    │   ├── number_line.py
    │   ├── numbers.py
    │   ├── probability.py
    │   ├── shape_matchers.py
    │   ├── svg
    │   │   ├── __init__.py
    │   │   ├── brace.py
    │   │   ├── drawings.py
    │   │   ├── old_tex_mobject.py
    │   │   ├── special_tex.py
    │   │   ├── string_mobject.py
    │   │   ├── svg_mobject.py
    │   │   ├── tex_mobject.py
    │   │   └── text_mobject.py
    │   ├── three_dimensions.py
    │   ├── types
    │   │   ├── __init__.py
    │   │   ├── dot_cloud.py
    │   │   ├── image_mobject.py
    │   │   ├── point_cloud_mobject.py
    │   │   ├── surface.py
    │   │   └── vectorized_mobject.py
    │   ├── value_tracker.py
    │   └── vector_field.py
    ├── module_loader.py
    ├── scene
    │   ├── __init__.py
    │   ├── interactive_scene.py
    │   ├── scene.py
    │   ├── scene_embed.py
    │   └── scene_file_writer.py
    ├── shader_wrapper.py
    ├── shaders
    │   ├── image
    │   │   ├── frag.glsl
    │   │   └── vert.glsl
    │   ├── inserts
    │   │   ├── NOTE.md
    │   │   ├── complex_functions.glsl
    │   │   ├── emit_gl_Position.glsl
    │   │   ├── finalize_color.glsl
    │   │   ├── get_unit_normal.glsl
    │   │   └── get_xyz_to_uv.glsl
    │   ├── mandelbrot_fractal
    │   │   ├── frag.glsl
    │   │   └── vert.glsl
    │   ├── newton_fractal
    │   │   ├── frag.glsl
    │   │   └── vert.glsl
    │   ├── quadratic_bezier
    │   │   ├── depth
    │   │   │   ├── frag.glsl
    │   │   │   ├── geom.glsl
    │   │   │   └── vert.glsl
    │   │   ├── fill
    │   │   │   ├── frag.glsl
    │   │   │   ├── geom.glsl
    │   │   │   └── vert.glsl
    │   │   └── stroke
    │   │   │   ├── frag.glsl
    │   │   │   ├── geom.glsl
    │   │   │   └── vert.glsl
    │   ├── simple_vert.glsl
    │   ├── surface
    │   │   ├── frag.glsl
    │   │   └── vert.glsl
    │   ├── textured_surface
    │   │   ├── frag.glsl
    │   │   └── vert.glsl
    │   └── true_dot
    │   │   ├── frag.glsl
    │   │   ├── geom.glsl
    │   │   └── vert.glsl
    ├── tex_templates.yml
    ├── typing.py
    ├── utils
    │   ├── __init__.py
    │   ├── bezier.py
    │   ├── cache.py
    │   ├── color.py
    │   ├── debug.py
    │   ├── dict_ops.py
    │   ├── directories.py
    │   ├── family_ops.py
    │   ├── file_ops.py
    │   ├── images.py
    │   ├── iterables.py
    │   ├── paths.py
    │   ├── rate_functions.py
    │   ├── shaders.py
    │   ├── simple_functions.py
    │   ├── sounds.py
    │   ├── space_ops.py
    │   ├── tex.py
    │   ├── tex_file_writing.py
    │   └── tex_to_symbol_count.py
    └── window.py
├── pyproject.toml
├── requirements.txt
├── setup.cfg
└── setup.py


/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | name: Bug report
 3 | about: Create a report to help us improve
 4 | title: ''
 5 | labels: bug
 6 | assignees: ''
 7 | 
 8 | ---
 9 | 
10 | ### Describe the bug
11 | <!-- A clear and concise description of what the bug is. -->
12 | 
13 | **Code**:
14 | <!-- The code you run which reflect the bug. -->
15 | 
16 | **Wrong display or Error traceback**:
17 | <!-- the wrong display result of the code you run, or the error Traceback -->
18 | 
19 | ### Additional context
20 | <!-- Add any other context about the problem here. -->
21 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: true
2 | contact_links:
3 |   - name: Ask A Question
4 |     url: https://github.com/3b1b/manim/discussions/categories/q-a
5 |     about: Please ask questions you encountered here.


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/error-when-using.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | name: Error when using
 3 | about: The error you encountered while using manim
 4 | title: ''
 5 | labels: ''
 6 | assignees: ''
 7 | 
 8 | ---
 9 | 
10 | ### Describe the error
11 | <!-- A clear and concise description of what you want to make. -->
12 | 
13 | ### Code and Error
14 | **Code**:
15 | <!-- The code you run -->
16 | 
17 | **Error**:
18 | <!-- The error traceback you get when run your code -->
19 | 
20 | ### Environment
21 | **OS System**: 
22 | **manim version**: master <!-- make sure you are using the latest version of master branch -->
23 | **python version**:
24 | 


--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
 1 | <!-- Thanks for contributing to manim!
 2 |     Please ensure that your pull request works with the latest version of manim.
 3 | -->
 4 | 
 5 | ## Motivation
 6 | <!-- Outline your motivation: In what way do your changes improve the library? -->
 7 | 
 8 | ## Proposed changes
 9 | <!-- What you changed in those files -->
10 | - 
11 | - 
12 | - 
13 | 
14 | ## Test
15 | <!-- How do you test your changes -->
16 | **Code**:
17 | 
18 | **Result**:


--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
 1 | name: docs
 2 | 
 3 | on: 
 4 |   push:
 5 |     paths:
 6 |       - 'docs/**'
 7 |   pull_request:
 8 |     paths:
 9 |       - 'docs/**'
10 | 
11 | jobs:
12 |   docs:
13 |     runs-on: ubuntu-latest
14 |     name: build up document and deploy
15 | 
16 |     steps:
17 |     - name: Checkout
18 |       uses: actions/checkout@master
19 |     
20 |     - name: Install sphinx and manim env
21 |       run: |
22 |         pip3 install --upgrade pip
23 |         sudo apt install python3-setuptools libpango1.0-dev
24 |         pip3 install -r docs/requirements.txt
25 |         pip3 install -r requirements.txt
26 |     
27 |     - name: Build document with Sphinx
28 |       run: |
29 |         cd docs
30 |         export PATH="$PATH:/home/runner/.local/bin"
31 |         export SPHINXBUILD="python3 -m sphinx"
32 |         make html
33 |         
34 |     - name: Deploy to GitHub pages
35 |       if: ${{ github.event_name == 'push' }}
36 |       uses: JamesIves/github-pages-deploy-action@3.7.1
37 |       with:
38 |         ACCESS_TOKEN: ${{ secrets.DOC_DEPLOY_TOKEN }}
39 |         BRANCH: gh-pages
40 |         FOLDER: docs/build/html
41 | 


--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
 1 | name: Upload Python Package
 2 | 
 3 | on:
 4 |   release:
 5 |     types: [created]
 6 | 
 7 | jobs:
 8 |   deploy:
 9 |     runs-on: ubuntu-latest
10 | 
11 |     strategy:
12 |       fail-fast: false
13 |       matrix:
14 |         python: ["py37", "py38", "py39", "py310"]
15 | 
16 |     steps:
17 |     - uses: actions/checkout@v2
18 | 
19 |     - name: Set up Python
20 |       uses: actions/setup-python@v2
21 |       with:
22 |         python-version: '3.8'
23 | 
24 |     - name: Install dependencies
25 |       run: |
26 |         python -m pip install --upgrade pip
27 |         pip install setuptools wheel twine build
28 | 
29 |     - name: Build wheels
30 |       run: python setup.py bdist_wheel --python-tag ${{ matrix.python }}
31 | 
32 |     - name: Upload wheels
33 |       env:
34 |         TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
35 |         TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
36 |       run: |
37 |         twine upload dist/*


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
  1 | # Created by https://www.toptal.com/developers/gitignore/api/python
  2 | # Edit at https://www.toptal.com/developers/gitignore?templates=python
  3 | 
  4 | ### Python ###
  5 | # Byte-compiled / optimized / DLL files
  6 | __pycache__/
  7 | *.py[cod]
  8 | *$py.class
  9 | 
 10 | # C extensions
 11 | *.so
 12 | 
 13 | # Distribution / packaging
 14 | .Python
 15 | build/
 16 | develop-eggs/
 17 | dist/
 18 | manimlib.egg-info/
 19 | 
 20 | downloads/
 21 | eggs/
 22 | .eggs/
 23 | lib/
 24 | lib64/
 25 | parts/
 26 | sdist/
 27 | var/
 28 | wheels/
 29 | pip-wheel-metadata/
 30 | share/python-wheels/
 31 | *.egg-info/
 32 | .installed.cfg
 33 | *.egg
 34 | MANIFEST
 35 | 
 36 | # PyInstaller
 37 | #  Usually these files are written by a python script from a template
 38 | #  before PyInstaller builds the exe, so as to inject date/other infos into it.
 39 | *.manifest
 40 | *.spec
 41 | 
 42 | # Installer logs
 43 | pip-log.txt
 44 | pip-delete-this-directory.txt
 45 | 
 46 | # Unit test / coverage reports
 47 | htmlcov/
 48 | .tox/
 49 | .nox/
 50 | .coverage
 51 | .coverage.*
 52 | .cache
 53 | nosetests.xml
 54 | coverage.xml
 55 | *.cover
 56 | *.py,cover
 57 | .hypothesis/
 58 | .pytest_cache/
 59 | pytestdebug.log
 60 | 
 61 | # Translations
 62 | *.mo
 63 | *.pot
 64 | 
 65 | # Django stuff:
 66 | *.log
 67 | local_settings.py
 68 | db.sqlite3
 69 | db.sqlite3-journal
 70 | 
 71 | # Flask stuff:
 72 | instance/
 73 | .webassets-cache
 74 | 
 75 | # Scrapy stuff:
 76 | .scrapy
 77 | 
 78 | # Sphinx documentation
 79 | docs/_build/
 80 | doc/_build/
 81 | 
 82 | # PyBuilder
 83 | target/
 84 | 
 85 | # Jupyter Notebook
 86 | .ipynb_checkpoints
 87 | 
 88 | # IPython
 89 | profile_default/
 90 | ipython_config.py
 91 | 
 92 | # pyenv
 93 | .python-version
 94 | pyrightconfig.json 
 95 | 
 96 | # pipenv
 97 | #   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
 98 | #   However, in case of collaboration, if having platform-specific dependencies or dependencies
 99 | #   having no cross-platform support, pipenv may install dependencies that don't work, or not
100 | #   install all needed dependencies.
101 | #Pipfile.lock
102 | 
103 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
104 | __pypackages__/
105 | 
106 | # Celery stuff
107 | celerybeat-schedule
108 | celerybeat.pid
109 | 
110 | # SageMath parsed files
111 | *.sage.py
112 | 
113 | # Environments
114 | .env
115 | .venv
116 | env/
117 | venv/
118 | ENV/
119 | env.bak/
120 | venv.bak/
121 | pythonenv*
122 | 
123 | # Spyder project settings
124 | .spyderproject
125 | .spyproject
126 | 
127 | # Rope project settings
128 | .ropeproject
129 | 
130 | # mkdocs documentation
131 | /site
132 | 
133 | # mypy
134 | .mypy_cache/
135 | .dmypy.json
136 | dmypy.json
137 | 
138 | # Pyre type checker
139 | .pyre/
140 | 
141 | # pytype static type analyzer
142 | .pytype/
143 | 
144 | # profiling data
145 | .prof
146 | 
147 | # End of https://www.toptal.com/developers/gitignore/api/python
148 | # Custom exclusions:
149 | .DS_Store
150 | 
151 | # For manim
152 | /videos
153 | /custom_config.yml
154 | test.py
155 | 


--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
 1 | MIT License
 2 | 
 3 | Copyright (c) 2020-2023 3Blue1Brown LLC
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | 


--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | graft manimlib
2 | recursive-exclude manimlib *.pyc *.DS_Store


--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
 1 | # Minimal makefile for Sphinx documentation
 2 | #
 3 | 
 4 | # You can set these variables from the command line, and also
 5 | # from the environment for the first two.
 6 | SPHINXOPTS    ?=
 7 | SPHINXBUILD   ?= sphinx-build
 8 | SOURCEDIR     = source
 9 | BUILDDIR      = build
10 | 
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | 	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 | 
15 | .PHONY: help Makefile
16 | 
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | 	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 | 


--------------------------------------------------------------------------------
/docs/example.py:
--------------------------------------------------------------------------------
 1 | from manimlib import *
 2 | 
 3 | class SquareToCircle(Scene):
 4 |     def construct(self):
 5 |         circle = Circle()
 6 |         circle.set_fill(BLUE, opacity=0.5)
 7 |         circle.set_stroke(BLUE_E, width=4)
 8 |         square = Square()
 9 | 
10 |         self.play(ShowCreation(square))
11 |         self.wait()
12 |         self.play(ReplacementTransform(square, circle))
13 |         self.wait()
14 |         # Try typing the following lines
15 |         # self.play(circle.animate.stretch(4, dim=0))
16 |         # self.play(Rotate(circle, TAU / 4))
17 |         # self.play(circle.animate.shift(2 * RIGHT), circle.animate.scale(0.25))
18 |         # circle.insert_n_curves(10)
19 |         # self.play(circle.animate.apply_complex_function(lambda z: z**2))
20 | 
21 | class SquareToCircleEmbed(Scene):
22 |     def construct(self):
23 |         circle = Circle()
24 |         circle.set_fill(BLUE, opacity=0.5)
25 |         circle.set_stroke(BLUE_E, width=4)
26 | 
27 |         self.add(circle)
28 |         self.wait()
29 |         self.play(circle.animate.stretch(4, dim=0))
30 |         self.wait(1.5)
31 |         self.play(Rotate(circle, TAU / 4))
32 |         self.wait(1.5)
33 |         self.play(circle.animate.shift(2 * RIGHT), circle.animate.scale(0.25))
34 |         self.wait(1.5)
35 |         circle.insert_n_curves(10)
36 |         self.play(circle.animate.apply_complex_function(lambda z: z**2))
37 |         self.wait(2)
38 | 


--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
 1 | @ECHO OFF
 2 | 
 3 | pushd %~dp0
 4 | 
 5 | REM Command file for Sphinx documentation
 6 | 
 7 | if "%SPHINXBUILD%" == "" (
 8 | 	set SPHINXBUILD=sphinx-build
 9 | )
10 | set SOURCEDIR=source
11 | set BUILDDIR=build
12 | 
13 | if "%1" == "" goto help
14 | 
15 | %SPHINXBUILD% >NUL 2>NUL
16 | if errorlevel 9009 (
17 | 	echo.
18 | 	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
19 | 	echo.installed, then set the SPHINXBUILD environment variable to point
20 | 	echo.to the full path of the 'sphinx-build' executable. Alternatively you
21 | 	echo.may add the Sphinx directory to PATH.
22 | 	echo.
23 | 	echo.If you don't have Sphinx installed, grab it from
24 | 	echo.http://sphinx-doc.org/
25 | 	exit /b 1
26 | )
27 | 
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 | 
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 | 
34 | :end
35 | popd
36 | 


--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | Sphinx==3.0.3
2 | sphinx-copybutton
3 | furo==2020.10.5b9
4 | Jinja2


--------------------------------------------------------------------------------
/docs/source/_static/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3b1b/manim/41613db7eca9eabbb40aa830631e5206c9282f0f/docs/source/_static/icon.png


--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
 1 | import os
 2 | import sys
 3 | sys.path.insert(0, os.path.abspath("."))
 4 | sys.path.insert(0, os.path.abspath('../../'))
 5 | 
 6 | 
 7 | project = 'manim'
 8 | copyright = '- This document has been placed in the public domain.'
 9 | author = 'TonyCrane'
10 | 
11 | release = ''
12 | 
13 | extensions = [
14 |     'sphinx.ext.todo',
15 |     'sphinx.ext.githubpages',
16 |     'sphinx.ext.mathjax',
17 |     'sphinx.ext.intersphinx',
18 |     'sphinx.ext.autodoc', 
19 |     'sphinx.ext.coverage',
20 |     'sphinx.ext.napoleon',
21 |     'sphinx_copybutton',
22 |     'manim_example_ext'
23 | ]
24 | 
25 | autoclass_content = 'both'
26 | mathjax_path = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"
27 | 
28 | templates_path = ['_templates']
29 | source_suffix = '.rst'
30 | master_doc = 'index'
31 | pygments_style = 'default'
32 | 
33 | html_static_path = ["_static"]
34 | html_css_files = [
35 |     "https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/custom.css", 
36 |     "https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/colors.css"
37 | ]
38 | html_theme = 'furo'  # pip install furo==2020.10.5b9
39 | html_favicon = '_static/icon.png'
40 | html_logo = '../../logo/transparent_graph.png'
41 | html_theme_options = {
42 |     "sidebar_hide_name": True,
43 | }
44 | 


--------------------------------------------------------------------------------
/docs/source/development/about.rst:
--------------------------------------------------------------------------------
 1 | About
 2 | =====
 3 | 
 4 | About Manim
 5 | -----------
 6 | 
 7 | Manim is an animation engine for explanatory math videos. 
 8 | You can use it to make math videos (or other fields) like 3Blue1Brown.
 9 | 
10 | There are mainly two versions here:
11 | 
12 | - `3b1b/manim <https://github.com/3b1b/manim>`_ : Maintained by Grant Sanderson of 3Blue1Brown.
13 | 
14 | Using OpenGL and its GLSL language to use GPU for rendering. It has higher efficiency, 
15 | faster rendering speed, and supports real-time rendering and interaction.
16 | 
17 | - `ManimCommunity/manim <https://github.com/ManimCommunity/manim>`_ : Maintained by Manim Community Dev Team.
18 | 
19 | Using multiple backend rendering. There is better documentation and 
20 | a more open contribution community.
21 | 
22 | About this documentation
23 | ------------------------
24 | 
25 | This documentation is based on the version in `3b1b/manim <https://github.com/3b1b/manim>`_. 
26 | Created by `TonyCrane <https://github.com/TonyCrane>`_ ("鹤翔万里" in Chinese) and in production.
27 | 
28 | Among them, the ``manim_example_ext`` extension for Sphinx refers to 
29 | `the documentation of ManimCommunity <https://docs.manim.community/>`_.
30 | 
31 | If you want to contribute to manim or this document, please see: :doc:`contributing`


--------------------------------------------------------------------------------
/docs/source/development/contributing.rst:
--------------------------------------------------------------------------------
 1 | Contributing
 2 | ============
 3 | 
 4 | Accept any contribution you make :)
 5 | 
 6 | - **Contribute to the manim source code**: 
 7 | 
 8 | Please fork to your own repository and make changes, submit a pull request, and fill in 
 9 | the motivation for the change following the instructions in the template. We will check 
10 | your pull request in detail (this usually takes a while, please be patient)
11 | 
12 | - **Contribute to the documentation**: 
13 | 
14 | Also submit a pull request and write down the main changes.
15 | 
16 | - **If you find a bug in the code**: 
17 | 
18 | Please open an issue and fill in the found problem and your environment according 
19 | to the template. (But please note that if you think this problem is just a problem 
20 | of yourself, rather than a problem of source code, it is recommended that you ask a 
21 | question in the `Q&A category <https://github.com/3b1b/manim/discussions/categories/q-a>`_ 
22 | of the discussion page)
23 | 
24 | - **You are welcome to share the content you made with manim**: 
25 | 
26 | Post it in the `show and tell category <https://github.com/3b1b/manim/discussions/categories/show-and-tell>`_
27 | of the discussion page.
28 | 
29 | - **You are also welcome to share some of your suggestions and ideas**: 
30 | 
31 | Post them in the `ideas category <https://github.com/3b1b/manim/discussions/categories/ideas>`_ 
32 | of the discussion page.
33 | 
34 | How to build this documentation
35 | -------------------------------
36 | 
37 | - Clone the 3b1b/manim repository
38 | 
39 | .. code-block:: sh
40 | 
41 |     git clone https://github.com/3b1b/manim.git
42 |     # or your own repo
43 |     # git clone https://github.com/<your user name>/manim.git
44 |     cd manim
45 | 
46 | - Install python package dependencies
47 | 
48 | .. code-block:: sh
49 | 
50 |     pip install -r docs/requirements.txt
51 | 
52 | - Go to the ``docs/`` folder and build
53 | 
54 | .. code-block:: sh
55 | 
56 |     cd docs/
57 |     make html
58 | 
59 | - The output document is located in ``docs/build/html/``


--------------------------------------------------------------------------------
/docs/source/documentation/animation/index.rst:
--------------------------------------------------------------------------------
1 | Animation (TODO)
2 | ================


--------------------------------------------------------------------------------
/docs/source/documentation/camera/index.rst:
--------------------------------------------------------------------------------
1 | Camera (TODO)
2 | =============


--------------------------------------------------------------------------------
/docs/source/documentation/custom_config.rst:
--------------------------------------------------------------------------------
  1 | custom_config
  2 | ==============
  3 | 
  4 | ``directories``
  5 | ---------------
  6 | 
  7 | - ``mirror_module_path``
  8 |     (``True`` or ``False``) Whether to create a folder named the name of the 
  9 |     running file under the ``output`` path, and save the output (``images/`` 
 10 |     or ``videos/``) in it.
 11 | 
 12 | - ``base``
 13 |     The root directory that will hold files, such as video files manim renders,
 14 |     or image resources that it pulls from
 15 | 
 16 | - ``output``
 17 |     Output file path, the videos will be saved in the ``videos/`` folder under it, 
 18 |     and the pictures will be saved in the ``images/`` folder under it.
 19 | 
 20 |     For example, if you set ``output`` to ``"/.../manim/output"`` and 
 21 |     ``mirror_module_path`` to ``False``, then you exported ``Scene1`` in the code 
 22 |     file and saved the last frame, then the final directory structure will be like:
 23 | 
 24 |     .. code-block:: text
 25 |         :emphasize-lines: 9, 11
 26 | 
 27 |             manim/
 28 |             ├── manimlib/
 29 |             │   ├── animation/
 30 |             │   ├── ...
 31 |             │   ├── default_config.yml
 32 |             │   └── window.py
 33 |             ├── output/
 34 |             │   ├── images
 35 |             │   │   └── Scene1.png
 36 |             │   └── videos
 37 |             │       └── Scene1.mp4
 38 |             ├── code.py
 39 |             └── custom_config.yml
 40 | 
 41 |     But if you set ``mirror_module_path`` to ``True``, the directory structure will be:
 42 | 
 43 |     .. code-block:: text
 44 |         :emphasize-lines: 8
 45 | 
 46 |             manim/
 47 |             ├── manimlib/
 48 |             │   ├── animation/
 49 |             │   ├── ...
 50 |             │   ├── default_config.yml
 51 |             │   └── window.py
 52 |             ├── output/
 53 |             │   └── code/
 54 |             │       ├── images
 55 |             │       │   └── Scene1.png
 56 |             │       └── videos
 57 |             │           └── Scene1.mp4
 58 |             ├── code.py
 59 |             └── custom_config.yml
 60 | 
 61 | - ``raster_images`` 
 62 |     The directory for storing raster images to be used in the code (including 
 63 |     ``.jpg``, ``.jpeg``, ``.png`` and ``.gif``), which will be read by ``ImageMobject``.
 64 | 
 65 | - ``vector_images``
 66 |     The directory for storing vector images to be used in the code (including 
 67 |     ``.svg`` and ``.xdv``), which will be read by ``SVGMobject``.
 68 | 
 69 | - ``sounds``
 70 |     The directory for storing sound files to be used in ``Scene.add_sound()`` (
 71 |     including ``.wav`` and ``.mp3``).
 72 | 
 73 | - ``cache``
 74 |     The directory for storing temporarily generated cache files, including 
 75 |     ``Tex`` cache, ``Text`` cache and storage of object points.
 76 | 
 77 | 
 78 | ``window``
 79 | ----------
 80 | 
 81 | - ``position_string``
 82 |     The relative position of the playback window on the display (two characters, 
 83 |     the first character means upper(U) / middle(O) / lower(D), the second character 
 84 |     means left(L) / middle(O) / right(R)).
 85 | 
 86 | - ``monitor_index``
 87 |     If using multiple monitors, which one should the window show up in?
 88 | 
 89 | - ``full_screen``
 90 |     Should the preview window be full screen. If not, it defaults to half the screen
 91 | 
 92 | - ``position``
 93 |     This is an option to more manually set the default window position, in pixel
 94 |     coordinates, e.g. (500, 300)
 95 | 
 96 | - ``size``
 97 |     Option to more manually set the default window size, in pixel coordinates,
 98 |     e.g. (1920, 1080)
 99 | 
100 | 
101 | ``camera``
102 | ----------
103 | 
104 | - ``resolution``
105 |     Resolution to render at, e.g. (1920, 1080)
106 | 
107 | - ``background_color``
108 |     Default background color of scenes
109 | 
110 | - ``fps``
111 |     Framerate
112 | 
113 | - ``background_opacity``
114 |     Opacity of the background
115 | 
116 | 
117 | ``file_writer``
118 | ---------------
119 | Configuration specifying how files are written, e.g. what ffmpeg parameters to use
120 | 
121 | 
122 | ``scene``
123 | -------
124 | Some default configuration for the Scene class
125 | 
126 | 
127 | ``text``
128 | -------
129 | 
130 | - ``font`` 
131 |     Default font of Text
132 | 
133 | - ``text_alignment``
134 |     Default text alignment for LaTeX
135 | 
136 | ``tex``
137 | -------
138 | 
139 | - ``template``
140 |     Which configuration from the manimlib/tex_template.yml file should be used
141 |     to determine the latex compiler to use, and what preamble to include for 
142 |     rendering tex. 
143 | 
144 | 
145 | ``sizes``
146 | ---------
147 | 
148 | Valuess for various constants used in manimm to specify distances, like the height
149 | of the frame, the value of SMALL_BUFF, LARGE_BUFF, etc.
150 | 
151 | 
152 | ``colors``
153 | ----------
154 | 
155 | Color pallete to use, determining values of color constants like RED, BLUE_E, TEAL, etc.
156 | 
157 | ``loglevel``
158 | ------------
159 | 
160 | Can be DEBUG / INFO / WARNING / ERROR / CRITICAL
161 | 
162 | 
163 | ``universal_import_line``
164 | -------------------------
165 | 
166 | Import line that need to execute when entering interactive mode directly.
167 | 
168 | 
169 | ``ignore_manimlib_modules_on_reload``
170 | -------------------------------------
171 | 
172 | When calling ``reload`` during the interactive mode, imported modules are
173 | by default reloaded, in case the user writing a scene which pulls from various
174 | other files they have written. By default, modules withinn the manim library will
175 | be ignored, but one developing manim may want to set this to be False so that 
176 | edits to the library are reloaded as well.
177 | 


--------------------------------------------------------------------------------
/docs/source/documentation/mobject/index.rst:
--------------------------------------------------------------------------------
1 | Mobject (TODO)
2 | ==============


--------------------------------------------------------------------------------
/docs/source/documentation/scene/index.rst:
--------------------------------------------------------------------------------
1 | Scene (TODO)
2 | ============


--------------------------------------------------------------------------------
/docs/source/documentation/shaders/index.rst:
--------------------------------------------------------------------------------
1 | Shaders (TODO)
2 | ==============


--------------------------------------------------------------------------------
/docs/source/documentation/utils/index.rst:
--------------------------------------------------------------------------------
1 | Utils (TODO)
2 | ============


--------------------------------------------------------------------------------
/docs/source/getting_started/configuration.rst:
--------------------------------------------------------------------------------
  1 | CLI flags and configuration
  2 | ===========================
  3 | 
  4 | Command Line Interface
  5 | ----------------------
  6 | 
  7 | To run manim, you need to enter the directory at the same level as ``manimlib/`` 
  8 | and enter the command in the following format into terminal:
  9 | 
 10 | .. code-block:: sh
 11 | 
 12 |     manimgl <code>.py <Scene> <flags>
 13 |     # or
 14 |     manim-render <code>.py <Scene> <flags>
 15 | 
 16 | - ``<code>.py`` : The python file you wrote. Needs to be at the same level as ``manimlib/``, otherwise you need to use an absolute path or a relative path.
 17 | - ``<Scene>`` : The scene you want to render here. If it is not written or written incorrectly, it will list all for you to choose. And if there is only one ``Scene`` in the file, this class will be rendered directly.
 18 | - ``<flags>`` : CLI flags.
 19 | 
 20 | Some useful flags
 21 | ^^^^^^^^^^^^^^^^^
 22 | 
 23 | - ``-w`` to write the scene to a file.
 24 | - ``-o`` to write the scene to a file and open the result.
 25 | - ``-s`` to skip to the end and just show the final frame. 
 26 | 
 27 |   - ``-so`` will save the final frame to an image and show it.
 28 | 
 29 | - ``-n <number>`` to skip ahead to the ``n``\ ’th animation of a scene. 
 30 | - ``-f`` to make the playback window fullscreen.
 31 | 
 32 | All supported flags
 33 | ^^^^^^^^^^^^^^^^^^^
 34 | 
 35 | ========================================================== ====== =====================================================================================================================================================================================================
 36 | flag                                                       abbr   function
 37 | ========================================================== ====== =====================================================================================================================================================================================================
 38 | ``--help``                                                 ``-h`` Show the help message and exit
 39 | ``--version``                                              ``-v`` Display the version of manimgl
 40 | ``--write_file``                                           ``-w`` Render the scene as a movie file
 41 | ``--skip_animations``                                      ``-s`` Skip to the last frame
 42 | ``--low_quality``                                          ``-l`` Render at a low quality (for faster rendering)
 43 | ``--medium_quality``                                       ``-m`` Render at a medium quality
 44 | ``--hd``                                                          Render at a 1080p quality
 45 | ``--uhd``                                                         Render at a 4k quality
 46 | ``--full_screen``                                          ``-f`` Show window in full screen
 47 | ``--presenter_mode``                                       ``-p`` Scene will stay paused during wait calls until space bar or right arrow is hit, like a slide show
 48 | ``--save_pngs``                                            ``-g`` Save each frame as a png
 49 | ``--gif``                                                  ``-i`` Save the video as gif
 50 | ``--transparent``                                          ``-t`` Render to a movie file with an alpha channel
 51 | ``--quiet``                                                ``-q``
 52 | ``--write_all``                                            ``-a`` Write all the scenes from a file
 53 | ``--open``                                                 ``-o`` Automatically open the saved file once its done
 54 | ``--finder``                                                      Show the output file in finder
 55 | ``--config``                                                      Guide for automatic configuration
 56 | ``--file_name FILE_NAME``                                         Name for the movie or image file
 57 | ``--start_at_animation_number START_AT_ANIMATION_NUMBER``  ``-n`` Start rendering not from the first animation, but from another, specified by its index. If you passing two comma separated values, e.g. "3,6", it will end the rendering at the second value.
 58 | ``--embed [EMBED]``                                        ``-e`` Creates a new file where the line ``self.embed`` is inserted into the Scenes construct method. If a string is passed in, the line will be inserted below the last line of code including that string.
 59 | ``--resolution RESOLUTION``                                ``-r`` Resolution, passed as "WxH", e.g. "1920x1080"
 60 | ``--fps FPS``                                                     Frame rate, as an integer
 61 | ``--color COLOR``                                          ``-c`` Background color
 62 | ``--leave_progress_bars``                                         Leave progress bars displayed in terminal
 63 | ``--video_dir VIDEO_DIR``                                         Directory to write video
 64 | ``--config_file CONFIG_FILE``                                     Path to the custom configuration file
 65 | ``--log-level LOG_LEVEL``                                         Level of messages to Display, can be DEBUG / INFO / WARNING / ERROR / CRITICAL
 66 | ``--autoreload``                                                  Automatically reload Python modules to pick up code changes across during an interactive embedding
 67 | ========================================================== ====== =====================================================================================================================================================================================================
 68 | 
 69 | custom_config
 70 | --------------
 71 | 
 72 | In order to perform more configuration (about directories, etc.) and permanently 
 73 | change the default value (you don't have to add flags to the command every time), 
 74 | you can modify ``custom_config.yml``. The meaning of each option is in 
 75 | page :doc:`../documentation/custom_config`.
 76 | 
 77 | You can also use different ``custom_config.yml`` for different directories, such as 
 78 | following the directory structure:
 79 | 
 80 | .. code-block:: text
 81 | 
 82 |     manim/
 83 |     ├── manimlib/
 84 |     │   ├── animation/
 85 |     │   ├── ...
 86 |     │   ├── default_config.yml
 87 |     │   └── window.py
 88 |     ├── project/
 89 |     │   ├── code.py
 90 |     │   └── custom_config.yml
 91 |     └── custom_config.yml
 92 | 
 93 | When you enter the ``project/`` folder and run ``manimgl code.py <Scene>``, 
 94 | it will overwrite ``manim/default_config.yml`` with ``custom_config.yml`` 
 95 | in the ``project`` folder.
 96 | 
 97 | Alternatively, you can use ``--config_file`` flag in CLI to specify configuration file manually.
 98 | 
 99 | .. code-block:: sh
100 | 
101 |     manimgl project/code.py --config_file /path/to/custom_config.yml


--------------------------------------------------------------------------------
/docs/source/getting_started/installation.rst:
--------------------------------------------------------------------------------
 1 | Installation
 2 | ============
 3 | 
 4 | Manim runs on Python 3.7 or higher.
 5 | 
 6 | System requirements are:
 7 | 
 8 | - `FFmpeg <https://ffmpeg.org/>`__
 9 | - `OpenGL <https://www.opengl.org//>`__ (included in python package ``PyOpenGL``)
10 | - `LaTeX <https://www.latex-project.org>`__ (optional, if you want to use LaTeX)
11 | - `Pango <https://pango.org>`__ (only for Linux)
12 | 
13 | 
14 | Install FFmpeg
15 | --------------
16 | 
17 | 
18 | 
19 | Install FFmpeg Windows
20 | ------------------------
21 | .. code-block:: cmd
22 | 
23 |    choco install ffmpeg
24 | 
25 | 
26 | # Install FFmepeg Linux
27 | ----------------------------
28 | .. code-block:: sh
29 | 
30 |    $ sudo apt update
31 |    $ sudo apt install ffmpeg
32 |    $ ffmpeg -version
33 |   
34 | # Install FFmpeg MacOS
35 | ----------------------------
36 | - Download This ZIP file `https://www.gyan.dev/ffmpeg/builds/ffmpeg-git-full.7z`(if the link is not working download this zip file from there original website)
37 | 
38 | 
39 | 
40 | Directly
41 | --------
42 | 
43 | .. code-block:: sh
44 | 
45 |    # Install manimgl
46 |    pip install manimgl
47 | 
48 |    # Try it out
49 |    manimgl
50 | 
51 | If you want to hack on manimlib itself, clone this repository and in
52 | that directory execute:
53 | 
54 | .. code-block:: sh
55 | 
56 |    # Install python requirements
57 |    pip install -e .
58 | 
59 |    # Try it out
60 |    manimgl example_scenes.py OpeningManimExample
61 |    # or
62 |    manim-render example_scenes.py OpeningManimExample
63 | 
64 | If you run the above command and no error message appears, 
65 | then you have successfully installed all the environments required by manim.
66 | 
67 | Directly (Windows)
68 | ------------------
69 | 
70 | 1. `Install
71 |    FFmpeg <https://www.wikihow.com/Install-FFmpeg-on-Windows>`__, and make sure that its path is in the PATH environment variable.
72 | 2. Install a LaTeX distribution.
73 |    `TeXLive-full <http://tug.org/texlive/>`__ is recommended.
74 | 3. Install the remaining Python packages.
75 | 
76 | .. code-block:: sh  
77 | 
78 |    git clone https://github.com/3b1b/manim.git
79 |    cd manim  
80 |    pip install -e . 
81 |    manimgl example_scenes.py OpeningManimExample
82 | 
83 | For Anaconda
84 | ------------
85 | 
86 | -  Install FFmpeg and LaTeX as above.
87 | -  Create a conda environment using
88 | 
89 | .. code-block:: sh
90 |    
91 |    git clone https://github.com/3b1b/manim.git
92 |    cd manim 
93 |    conda create -n manim python=3.8
94 |    conda activate manim
95 |    pip install -e .
96 | 


--------------------------------------------------------------------------------
/docs/source/index.rst:
--------------------------------------------------------------------------------
 1 | Manim's documentation
 2 | =====================
 3 | 
 4 | .. image:: https://cdn.jsdelivr.net/gh/3b1b/manim@master/logo/white_with_name.png
 5 | 
 6 | Manim is an animation engine for explanatory math videos. It's used to create precise animations programmatically, as seen in the videos
 7 | at `3Blue1Brown <https://www.3blue1brown.com/>`_.
 8 | 
 9 | And here is a Chinese version of this documentation: https://docs.manim.org.cn/
10 | 
11 | .. toctree::
12 |    :maxdepth: 2
13 |    :caption: Getting Started
14 | 
15 |    getting_started/installation
16 |    getting_started/quickstart
17 |    getting_started/configuration
18 |    getting_started/example_scenes
19 |    getting_started/config
20 |    getting_started/structure
21 |    getting_started/whatsnew
22 | 
23 | .. toctree::
24 |    :maxdepth: 2
25 |    :caption: Documentation
26 | 
27 |    documentation/constants
28 |    documentation/custom_config
29 | 
30 | .. toctree::
31 |    :maxdepth: 2
32 |    :caption: Development
33 | 
34 |    development/changelog
35 |    development/contributing
36 |    development/about
37 | 


--------------------------------------------------------------------------------
/docs/source/manim_example_ext.py:
--------------------------------------------------------------------------------
  1 | from docutils import nodes
  2 | from docutils.parsers.rst import directives, Directive
  3 | 
  4 | import jinja2
  5 | import os
  6 | 
  7 | 
  8 | class skip_manim_node(nodes.Admonition, nodes.Element):
  9 |     pass
 10 | 
 11 | 
 12 | def visit(self, node, name=""):
 13 |     self.visit_admonition(node, name)
 14 | 
 15 | 
 16 | def depart(self, node):
 17 |     self.depart_admonition(node)
 18 | 
 19 | 
 20 | class ManimExampleDirective(Directive):
 21 |     has_content = True
 22 |     required_arguments = 1
 23 |     optional_arguments = 0
 24 |     option_spec = {
 25 |         "hide_code": bool,
 26 |         "media": str,
 27 |     }
 28 |     final_argument_whitespace = True
 29 | 
 30 |     def run(self):
 31 |         hide_code = "hide_code" in self.options
 32 |         scene_name = self.arguments[0]
 33 |         media_file_name = self.options["media"]
 34 | 
 35 |         source_block = [
 36 |             ".. code-block:: python",
 37 |             "",
 38 |             *["    " + line for line in self.content],
 39 |         ]
 40 |         source_block = "\n".join(source_block)
 41 | 
 42 |         state_machine = self.state_machine
 43 |         document = state_machine.document
 44 | 
 45 |         if any(media_file_name.endswith(ext) for ext in [".png", ".jpg", ".gif"]):
 46 |             is_video = False
 47 |         else:
 48 |             is_video = True
 49 | 
 50 |         rendered_template = jinja2.Template(TEMPLATE).render(
 51 |             scene_name=scene_name,
 52 |             scene_name_lowercase=scene_name.lower(),
 53 |             hide_code=hide_code,
 54 |             is_video=is_video,
 55 |             media_file_name=media_file_name,
 56 |             source_block=source_block,
 57 |         )
 58 |         state_machine.insert_input(
 59 |             rendered_template.split("\n"), source=document.attributes["source"]
 60 |         )
 61 | 
 62 |         return []
 63 | 
 64 | 
 65 | def setup(app):
 66 |     app.add_node(skip_manim_node, html=(visit, depart))
 67 | 
 68 |     setup.app = app
 69 |     setup.config = app.config
 70 |     setup.confdir = app.confdir
 71 | 
 72 |     app.add_directive("manim-example", ManimExampleDirective)
 73 | 
 74 |     metadata = {"parallel_read_safe": False, "parallel_write_safe": True}
 75 |     return metadata
 76 | 
 77 | 
 78 | TEMPLATE = r"""
 79 | {% if not hide_code %}
 80 | 
 81 | .. raw:: html
 82 | 
 83 |     <div class="manim-example">
 84 | 
 85 | {% endif %}
 86 | 
 87 | {% if is_video %}
 88 | .. raw:: html
 89 | 
 90 |     <video id="{{ scene_name_lowercase }}" class="manim-video" controls loop autoplay src="{{ media_file_name }}"></video>
 91 | {% else %}
 92 | .. image:: {{ media_file_name }}
 93 |     :align: center
 94 |     :name: {{ scene_name_lowercase }}
 95 | {% endif %}
 96 | 
 97 | {% if not hide_code %}
 98 | .. raw:: html
 99 | 
100 |     <h5 class="example-header">{{ scene_name }}<a class="headerlink" href="#{{ scene_name_lowercase }}">¶</a></h5>
101 | 
102 | {{ source_block }}
103 | {% endif %}
104 | 
105 | .. raw:: html
106 | 
107 |     </div>
108 | """


--------------------------------------------------------------------------------
/logo/cropped.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3b1b/manim/41613db7eca9eabbb40aa830631e5206c9282f0f/logo/cropped.png


--------------------------------------------------------------------------------
/logo/graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3b1b/manim/41613db7eca9eabbb40aa830631e5206c9282f0f/logo/graph.png


--------------------------------------------------------------------------------
/logo/logo.py:
--------------------------------------------------------------------------------
  1 | from manimlib.imports import *
  2 | 
  3 | NEW_BLUE = "#68a8e1"
  4 | 
  5 | class Thumbnail(GraphScene):
  6 |     CONFIG = {
  7 |         "y_max": 8,
  8 |         "y_axis_height": 5,
  9 |     }
 10 | 
 11 |     def construct(self):
 12 |         self.show_function_graph()
 13 | 
 14 |     def show_function_graph(self):
 15 |         self.setup_axes(animate=False)
 16 |         def func(x):
 17 |             return 0.1 * (x + 3-5) * (x - 3-5) * (x-5) + 5
 18 | 
 19 |         def rect(x):
 20 |             return 2.775*(x-1.5)+3.862
 21 |         recta = self.get_graph(rect,x_min=-1,x_max=5)
 22 |         graph = self.get_graph(func,x_min=0.2,x_max=9)
 23 |         graph.set_color(NEW_BLUE)
 24 |         input_tracker_p1 = ValueTracker(1.5)
 25 |         input_tracker_p2 = ValueTracker(3.5)
 26 | 
 27 |         def get_x_value(input_tracker):
 28 |             return input_tracker.get_value()
 29 | 
 30 |         def get_y_value(input_tracker):
 31 |             return graph.underlying_function(get_x_value(input_tracker))
 32 | 
 33 |         def get_x_point(input_tracker):
 34 |             return self.coords_to_point(get_x_value(input_tracker), 0)
 35 | 
 36 |         def get_y_point(input_tracker):
 37 |             return self.coords_to_point(0, get_y_value(input_tracker))
 38 | 
 39 |         def get_graph_point(input_tracker):
 40 |             return self.coords_to_point(get_x_value(input_tracker), get_y_value(input_tracker))
 41 | 
 42 |         def get_v_line(input_tracker):
 43 |             return DashedLine(get_x_point(input_tracker), get_graph_point(input_tracker), stroke_width=2)
 44 | 
 45 |         def get_h_line(input_tracker):
 46 |             return DashedLine(get_graph_point(input_tracker), get_y_point(input_tracker), stroke_width=2)
 47 |         # 
 48 |         input_triangle_p1 = RegularPolygon(n=3, start_angle=TAU / 4)
 49 |         output_triangle_p1 = RegularPolygon(n=3, start_angle=0)
 50 |         for triangle in input_triangle_p1, output_triangle_p1:
 51 |             triangle.set_fill(WHITE, 1)
 52 |             triangle.set_stroke(width=0)
 53 |             triangle.scale(0.1)
 54 |         # 
 55 |         input_triangle_p2 = RegularPolygon(n=3, start_angle=TAU / 4)
 56 |         output_triangle_p2 = RegularPolygon(n=3, start_angle=0)
 57 |         for triangle in input_triangle_p2, output_triangle_p2:
 58 |             triangle.set_fill(WHITE, 1)
 59 |             triangle.set_stroke(width=0)
 60 |             triangle.scale(0.1)
 61 |         
 62 |         # 
 63 |         x_label_p1 = Tex("a")
 64 |         output_label_p1 = Tex("f(a)")
 65 |         x_label_p2 = Tex("b")
 66 |         output_label_p2 = Tex("f(b)")
 67 |         v_line_p1 = get_v_line(input_tracker_p1)
 68 |         v_line_p2 = get_v_line(input_tracker_p2)
 69 |         h_line_p1 = get_h_line(input_tracker_p1)
 70 |         h_line_p2 = get_h_line(input_tracker_p2)
 71 |         graph_dot_p1 = Dot(color=WHITE)
 72 |         graph_dot_p2 = Dot(color=WHITE)
 73 | 
 74 |         # reposition mobjects
 75 |         x_label_p1.next_to(v_line_p1, DOWN)
 76 |         x_label_p2.next_to(v_line_p2, DOWN)
 77 |         output_label_p1.next_to(h_line_p1, LEFT)
 78 |         output_label_p2.next_to(h_line_p2, LEFT)
 79 |         input_triangle_p1.next_to(v_line_p1, DOWN, buff=0)
 80 |         input_triangle_p2.next_to(v_line_p2, DOWN, buff=0)
 81 |         output_triangle_p1.next_to(h_line_p1, LEFT, buff=0)
 82 |         output_triangle_p2.next_to(h_line_p2, LEFT, buff=0)
 83 |         graph_dot_p1.move_to(get_graph_point(input_tracker_p1))
 84 |         graph_dot_p2.move_to(get_graph_point(input_tracker_p2))
 85 | 
 86 | 
 87 |         #
 88 |         self.play(
 89 |             ShowCreation(graph),
 90 |         )
 91 |         # Animacion del punto a
 92 |         self.add_foreground_mobject(graph_dot_p1)
 93 |         self.add_foreground_mobject(graph_dot_p2)
 94 |         self.play(
 95 |             DrawBorderThenFill(input_triangle_p1),
 96 |             Write(x_label_p1),
 97 |             ShowCreation(v_line_p1),
 98 |             GrowFromCenter(graph_dot_p1),
 99 |             ShowCreation(h_line_p1),
100 |             Write(output_label_p1),
101 |             DrawBorderThenFill(output_triangle_p1),
102 |             DrawBorderThenFill(input_triangle_p2),
103 |             Write(x_label_p2),
104 |             ShowCreation(v_line_p2),
105 |             GrowFromCenter(graph_dot_p2),
106 |             ShowCreation(h_line_p2),
107 |             Write(output_label_p2),
108 |             DrawBorderThenFill(output_triangle_p2),
109 |             run_time=0.5
110 |         )
111 |         self.add(
112 |             input_triangle_p2,
113 |             x_label_p2,
114 |             graph_dot_p2,
115 |             v_line_p2,
116 |             h_line_p2,
117 |             output_triangle_p2,
118 |             output_label_p2,
119 |         )
120 |         ###################
121 |         pendiente_recta = self.get_secant_slope_group(
122 |             1.9, recta, dx = 1.4,
123 |             df_label = None,
124 |             dx_label = None,
125 |             dx_line_color = PURPLE,
126 |             df_line_color= ORANGE,
127 |             )
128 |         grupo_secante = self.get_secant_slope_group(
129 |             1.5, graph, dx = 2,
130 |             df_label = None,
131 |             dx_label = None,
132 |             dx_line_color = "#942357",
133 |             df_line_color= "#3f7d5c",
134 |             secant_line_color = RED,
135 |         )
136 | 
137 | 
138 |         self.add(
139 |             input_triangle_p2,
140 |             graph_dot_p2,
141 |             v_line_p2,
142 |             h_line_p2,
143 |             output_triangle_p2,
144 |         )
145 |         self.play(FadeIn(grupo_secante))
146 | 
147 |         kwargs = {
148 |             "x_min" : 4,
149 |             "x_max" : 9,
150 |             "fill_opacity" : 0.75,
151 |             "stroke_width" : 0.25,
152 |         }
153 |         self.graph=graph
154 |         iteraciones=6
155 | 
156 | 
157 |         self.rect_list = self.get_riemann_rectangles_list(
158 |             graph, iteraciones,start_color=PURPLE,end_color=ORANGE, **kwargs
159 |         )
160 |         flat_rects = self.get_riemann_rectangles(
161 |             self.get_graph(lambda x : 0), dx = 0.5,start_color=invert_color(PURPLE),end_color=invert_color(ORANGE),**kwargs
162 |         )
163 |         rects = self.rect_list[0]
164 |         self.transform_between_riemann_rects(
165 |             flat_rects, rects, 
166 |             replace_mobject_with_target_in_scene = True,
167 |             run_time=0.9
168 |         )
169 | 
170 |         # adding manim
171 |         picture = Group(*self.mobjects)
172 |         picture.scale(0.6).to_edge(LEFT, buff=SMALL_BUFF)
173 |         manim = TexText("Manim").set_height(1.5) \
174 |                                     .next_to(picture, RIGHT) \
175 |                                     .shift(DOWN * 0.7)
176 |         self.add(manim)
177 | 


--------------------------------------------------------------------------------
/logo/transparent_graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3b1b/manim/41613db7eca9eabbb40aa830631e5206c9282f0f/logo/transparent_graph.png


--------------------------------------------------------------------------------
/logo/white_with_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3b1b/manim/41613db7eca9eabbb40aa830631e5206c9282f0f/logo/white_with_name.png


--------------------------------------------------------------------------------
/logo/with_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3b1b/manim/41613db7eca9eabbb40aa830631e5206c9282f0f/logo/with_name.png


--------------------------------------------------------------------------------
/logo/with_subtext.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3b1b/manim/41613db7eca9eabbb40aa830631e5206c9282f0f/logo/with_subtext.png


--------------------------------------------------------------------------------
/manimlib/__init__.py:
--------------------------------------------------------------------------------
 1 | import pkg_resources
 2 | 
 3 | __version__ = pkg_resources.get_distribution("manimgl").version
 4 | 
 5 | from typing import TYPE_CHECKING
 6 | 
 7 | if TYPE_CHECKING:
 8 |     from manimlib.typing import *
 9 | 
10 | from manimlib.constants import *
11 | 
12 | from manimlib.window import *
13 | 
14 | from manimlib.animation.animation import *
15 | from manimlib.animation.composition import *
16 | from manimlib.animation.creation import *
17 | from manimlib.animation.fading import *
18 | from manimlib.animation.growing import *
19 | from manimlib.animation.indication import *
20 | from manimlib.animation.movement import *
21 | from manimlib.animation.numbers import *
22 | from manimlib.animation.rotation import *
23 | from manimlib.animation.specialized import *
24 | from manimlib.animation.transform import *
25 | from manimlib.animation.transform_matching_parts import *
26 | from manimlib.animation.update import *
27 | 
28 | from manimlib.camera.camera import *
29 | 
30 | from manimlib.mobject.boolean_ops import *
31 | from manimlib.mobject.changing import *
32 | from manimlib.mobject.coordinate_systems import *
33 | from manimlib.mobject.frame import *
34 | from manimlib.mobject.functions import *
35 | from manimlib.mobject.geometry import *
36 | from manimlib.mobject.interactive import *
37 | from manimlib.mobject.matrix import *
38 | from manimlib.mobject.mobject import *
39 | from manimlib.mobject.mobject_update_utils import *
40 | from manimlib.mobject.number_line import *
41 | from manimlib.mobject.numbers import *
42 | from manimlib.mobject.probability import *
43 | from manimlib.mobject.shape_matchers import *
44 | from manimlib.mobject.svg.brace import *
45 | from manimlib.mobject.svg.drawings import *
46 | from manimlib.mobject.svg.string_mobject import *
47 | from manimlib.mobject.svg.svg_mobject import *
48 | from manimlib.mobject.svg.special_tex import *
49 | from manimlib.mobject.svg.tex_mobject import *
50 | from manimlib.mobject.svg.text_mobject import *
51 | from manimlib.mobject.three_dimensions import *
52 | from manimlib.mobject.types.dot_cloud import *
53 | from manimlib.mobject.types.image_mobject import *
54 | from manimlib.mobject.types.point_cloud_mobject import *
55 | from manimlib.mobject.types.surface import *
56 | from manimlib.mobject.types.vectorized_mobject import *
57 | from manimlib.mobject.value_tracker import *
58 | from manimlib.mobject.vector_field import *
59 | 
60 | from manimlib.scene.interactive_scene import *
61 | from manimlib.scene.scene import *
62 | 
63 | from manimlib.utils.bezier import *
64 | from manimlib.utils.cache import *
65 | from manimlib.utils.color import *
66 | from manimlib.utils.dict_ops import *
67 | from manimlib.utils.debug import *
68 | from manimlib.utils.directories import *
69 | from manimlib.utils.file_ops import *
70 | from manimlib.utils.images import *
71 | from manimlib.utils.iterables import *
72 | from manimlib.utils.paths import *
73 | from manimlib.utils.rate_functions import *
74 | from manimlib.utils.simple_functions import *
75 | from manimlib.utils.shaders import *
76 | from manimlib.utils.sounds import *
77 | from manimlib.utils.space_ops import *
78 | from manimlib.utils.tex import *
79 | 


--------------------------------------------------------------------------------
/manimlib/__main__.py:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env python
 2 | from addict import Dict
 3 | 
 4 | from manimlib import __version__
 5 | from manimlib.config import manim_config
 6 | from manimlib.config import parse_cli
 7 | import manimlib.extract_scene
 8 | from manimlib.utils.cache import clear_cache
 9 | from manimlib.window import Window
10 | 
11 | 
12 | from IPython.terminal.embed import KillEmbedded
13 | 
14 | 
15 | from typing import TYPE_CHECKING
16 | if TYPE_CHECKING:
17 |     from argparse import Namespace
18 | 
19 | 
20 | def run_scenes():
21 |     """
22 |     Runs the scenes in a loop and detects when a scene reload is requested.
23 |     """
24 |     # Create a new dict to be able to upate without
25 |     # altering global configuration
26 |     scene_config = Dict(manim_config.scene)
27 |     run_config = manim_config.run
28 | 
29 |     if run_config.show_in_window:
30 |         # Create a reusable window
31 |         window = Window(**manim_config.window)
32 |         scene_config.update(window=window)
33 | 
34 |     while True:
35 |         try:
36 |             # Blocking call since a scene may init an IPython shell()
37 |             scenes = manimlib.extract_scene.main(scene_config, run_config)
38 |             for scene in scenes:
39 |                 scene.run()
40 |             return
41 |         except KillEmbedded:
42 |             # Requested via the `exit_raise` IPython runline magic
43 |             # by means of the reload_scene() command
44 |             pass
45 |         except KeyboardInterrupt:
46 |             break
47 | 
48 | 
49 | def main():
50 |     """
51 |     Main entry point for ManimGL.
52 |     """
53 |     print(f"ManimGL \033[32mv{__version__}\033[0m")
54 | 
55 |     args = parse_cli()
56 |     if args.version and args.file is None:
57 |         return
58 |     if args.clear_cache:
59 |         clear_cache()
60 | 
61 |     run_scenes()
62 | 
63 | 
64 | if __name__ == "__main__":
65 |     main()
66 | 


--------------------------------------------------------------------------------
/manimlib/animation/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3b1b/manim/41613db7eca9eabbb40aa830631e5206c9282f0f/manimlib/animation/__init__.py


--------------------------------------------------------------------------------
/manimlib/animation/composition.py:
--------------------------------------------------------------------------------
  1 | from __future__ import annotations
  2 | 
  3 | from manimlib.animation.animation import Animation
  4 | from manimlib.animation.animation import prepare_animation
  5 | from manimlib.mobject.mobject import _AnimationBuilder
  6 | from manimlib.mobject.mobject import Group
  7 | from manimlib.mobject.types.vectorized_mobject import VGroup
  8 | from manimlib.mobject.types.vectorized_mobject import VMobject
  9 | from manimlib.utils.bezier import integer_interpolate
 10 | from manimlib.utils.bezier import interpolate
 11 | from manimlib.utils.iterables import remove_list_redundancies
 12 | from manimlib.utils.simple_functions import clip
 13 | 
 14 | from typing import TYPE_CHECKING, Union, Iterable
 15 | AnimationType = Union[Animation, _AnimationBuilder]
 16 | 
 17 | if TYPE_CHECKING:
 18 |     from typing import Callable, Optional
 19 | 
 20 |     from manimlib.mobject.mobject import Mobject
 21 |     from manimlib.scene.scene import Scene
 22 | 
 23 | 
 24 | DEFAULT_LAGGED_START_LAG_RATIO = 0.05
 25 | 
 26 | 
 27 | class AnimationGroup(Animation):
 28 |     def __init__(
 29 |         self,
 30 |         *args: AnimationType | Iterable[AnimationType],
 31 |         run_time: float = -1,  # If negative, default to sum of inputed animation runtimes
 32 |         lag_ratio: float = 0.0,
 33 |         group: Optional[Mobject] = None,
 34 |         group_type: Optional[type] = None,
 35 |         **kwargs
 36 |     ):
 37 |         animations = args[0] if isinstance(args[0], Iterable) else args
 38 |         self.animations = [prepare_animation(anim) for anim in animations]
 39 |         self.build_animations_with_timings(lag_ratio)
 40 |         self.max_end_time = max((awt[2] for awt in self.anims_with_timings), default=0)
 41 |         self.run_time = self.max_end_time if run_time < 0 else run_time
 42 |         self.lag_ratio = lag_ratio
 43 |         mobs = remove_list_redundancies([a.mobject for a in self.animations])
 44 |         if group is not None:
 45 |             self.group = group
 46 |         elif group_type is not None:
 47 |             self.group = group_type(*mobs)
 48 |         elif all(isinstance(anim.mobject, VMobject) for anim in animations):
 49 |             self.group = VGroup(*mobs)
 50 |         else:
 51 |             self.group = Group(*mobs)
 52 | 
 53 |         super().__init__(
 54 |             self.group,
 55 |             run_time=self.run_time,
 56 |             lag_ratio=lag_ratio,
 57 |             **kwargs
 58 |         )
 59 | 
 60 |     def get_all_mobjects(self) -> Mobject:
 61 |         return self.group
 62 | 
 63 |     def begin(self) -> None:
 64 |         self.group.set_animating_status(True)
 65 |         for anim in self.animations:
 66 |             anim.begin()
 67 |         # self.init_run_time()
 68 | 
 69 |     def finish(self) -> None:
 70 |         self.group.set_animating_status(False)
 71 |         for anim in self.animations:
 72 |             anim.finish()
 73 | 
 74 |     def clean_up_from_scene(self, scene: Scene) -> None:
 75 |         for anim in self.animations:
 76 |             anim.clean_up_from_scene(scene)
 77 | 
 78 |     def update_mobjects(self, dt: float) -> None:
 79 |         for anim in self.animations:
 80 |             anim.update_mobjects(dt)
 81 | 
 82 |     def calculate_max_end_time(self) -> None:
 83 |         self.max_end_time = max(
 84 |             (awt[2] for awt in self.anims_with_timings),
 85 |             default=0,
 86 |         )
 87 |         if self.run_time < 0:
 88 |             self.run_time = self.max_end_time
 89 | 
 90 |     def build_animations_with_timings(self, lag_ratio: float) -> None:
 91 |         """
 92 |         Creates a list of triplets of the form
 93 |         (anim, start_time, end_time)
 94 |         """
 95 |         self.anims_with_timings = []
 96 |         curr_time = 0
 97 |         for anim in self.animations:
 98 |             start_time = curr_time
 99 |             end_time = start_time + anim.get_run_time()
100 |             self.anims_with_timings.append(
101 |                 (anim, start_time, end_time)
102 |             )
103 |             # Start time of next animation is based on the lag_ratio
104 |             curr_time = interpolate(
105 |                 start_time, end_time, lag_ratio
106 |             )
107 | 
108 |     def interpolate(self, alpha: float) -> None:
109 |         # Note, if the run_time of AnimationGroup has been
110 |         # set to something other than its default, these
111 |         # times might not correspond to actual times,
112 |         # e.g. of the surrounding scene.  Instead they'd
113 |         # be a rescaled version.  But that's okay!
114 |         time = alpha * self.max_end_time
115 |         for anim, start_time, end_time in self.anims_with_timings:
116 |             anim_time = end_time - start_time
117 |             if anim_time == 0:
118 |                 sub_alpha = 0
119 |             else:
120 |                 sub_alpha = clip((time - start_time) / anim_time, 0, 1)
121 |             anim.interpolate(sub_alpha)
122 | 
123 | 
124 | class Succession(AnimationGroup):
125 |     def __init__(
126 |         self,
127 |         *animations: Animation,
128 |         lag_ratio: float = 1.0,
129 |         **kwargs
130 |     ):
131 |         super().__init__(*animations, lag_ratio=lag_ratio, **kwargs)
132 | 
133 |     def begin(self) -> None:
134 |         assert len(self.animations) > 0
135 |         self.active_animation = self.animations[0]
136 |         self.active_animation.begin()
137 | 
138 |     def finish(self) -> None:
139 |         self.active_animation.finish()
140 | 
141 |     def update_mobjects(self, dt: float) -> None:
142 |         self.active_animation.update_mobjects(dt)
143 | 
144 |     def interpolate(self, alpha: float) -> None:
145 |         index, subalpha = integer_interpolate(
146 |             0, len(self.animations), alpha
147 |         )
148 |         animation = self.animations[index]
149 |         if animation is not self.active_animation:
150 |             self.active_animation.finish()
151 |             animation.begin()
152 |             self.active_animation = animation
153 |         animation.interpolate(subalpha)
154 | 
155 | 
156 | class LaggedStart(AnimationGroup):
157 |     def __init__(
158 |         self,
159 |         *animations,
160 |         lag_ratio: float = DEFAULT_LAGGED_START_LAG_RATIO,
161 |         **kwargs
162 |     ):
163 |         super().__init__(*animations, lag_ratio=lag_ratio, **kwargs)
164 | 
165 | 
166 | class LaggedStartMap(LaggedStart):
167 |     def __init__(
168 |         self,
169 |         anim_func: Callable[[Mobject], Animation],
170 |         group: Mobject,
171 |         run_time: float = 2.0,
172 |         lag_ratio: float = DEFAULT_LAGGED_START_LAG_RATIO,
173 |         **kwargs
174 |     ):
175 |         anim_kwargs = dict(kwargs)
176 |         anim_kwargs.pop("lag_ratio", None)
177 |         super().__init__(
178 |             *(anim_func(submob, **anim_kwargs) for submob in group),
179 |             run_time=run_time,
180 |             lag_ratio=lag_ratio,
181 |             group=group
182 |         )
183 | 


--------------------------------------------------------------------------------
/manimlib/animation/growing.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from manimlib.animation.transform import Transform
 4 | 
 5 | from typing import TYPE_CHECKING
 6 | 
 7 | if TYPE_CHECKING:
 8 |     import numpy as np
 9 | 
10 |     from manimlib.mobject.geometry import Arrow
11 |     from manimlib.mobject.mobject import Mobject
12 |     from manimlib.typing import ManimColor
13 | 
14 | 
15 | class GrowFromPoint(Transform):
16 |     def __init__(
17 |         self,
18 |         mobject: Mobject,
19 |         point: np.ndarray,
20 |         point_color: ManimColor = None,
21 |         **kwargs
22 |     ):
23 |         self.point = point
24 |         self.point_color = point_color
25 |         super().__init__(mobject, **kwargs)
26 | 
27 |     def create_target(self) -> Mobject:
28 |         return self.mobject.copy()
29 | 
30 |     def create_starting_mobject(self) -> Mobject:
31 |         start = super().create_starting_mobject()
32 |         start.scale(0)
33 |         start.move_to(self.point)
34 |         if self.point_color is not None:
35 |             start.set_color(self.point_color)
36 |         return start
37 | 
38 | 
39 | class GrowFromCenter(GrowFromPoint):
40 |     def __init__(self, mobject: Mobject, **kwargs):
41 |         point = mobject.get_center()
42 |         super().__init__(mobject, point, **kwargs)
43 | 
44 | 
45 | class GrowFromEdge(GrowFromPoint):
46 |     def __init__(self, mobject: Mobject, edge: np.ndarray, **kwargs):
47 |         point = mobject.get_bounding_box_point(edge)
48 |         super().__init__(mobject, point, **kwargs)
49 | 
50 | 
51 | class GrowArrow(GrowFromPoint):
52 |     def __init__(self, arrow: Arrow, **kwargs):
53 |         point = arrow.get_start()
54 |         super().__init__(arrow, point, **kwargs)
55 | 


--------------------------------------------------------------------------------
/manimlib/animation/movement.py:
--------------------------------------------------------------------------------
  1 | from __future__ import annotations
  2 | 
  3 | from manimlib.animation.animation import Animation
  4 | from manimlib.utils.rate_functions import linear
  5 | 
  6 | from typing import TYPE_CHECKING
  7 | 
  8 | if TYPE_CHECKING:
  9 |     from typing import Callable, Sequence
 10 | 
 11 |     import numpy as np
 12 | 
 13 |     from manimlib.mobject.mobject import Mobject
 14 |     from manimlib.mobject.types.vectorized_mobject import VMobject
 15 | 
 16 | 
 17 | class Homotopy(Animation):
 18 |     apply_function_config: dict = dict()
 19 | 
 20 |     def __init__(
 21 |         self,
 22 |         homotopy: Callable[[float, float, float, float], Sequence[float]],
 23 |         mobject: Mobject,
 24 |         run_time: float = 3.0,
 25 |         **kwargs
 26 |     ):
 27 |         """
 28 |         Homotopy is a function from
 29 |         (x, y, z, t) to (x', y', z')
 30 |         """
 31 |         self.homotopy = homotopy
 32 |         super().__init__(mobject, run_time=run_time, **kwargs)
 33 | 
 34 |     def function_at_time_t(self, t: float) -> Callable[[np.ndarray], Sequence[float]]:
 35 |         def result(p):
 36 |             return self.homotopy(*p, t)
 37 |         return result
 38 | 
 39 |     def interpolate_submobject(
 40 |         self,
 41 |         submob: Mobject,
 42 |         start: Mobject,
 43 |         alpha: float
 44 |     ) -> None:
 45 |         submob.match_points(start)
 46 |         submob.apply_function(
 47 |             self.function_at_time_t(alpha),
 48 |             **self.apply_function_config
 49 |         )
 50 | 
 51 | 
 52 | class SmoothedVectorizedHomotopy(Homotopy):
 53 |     apply_function_config: dict = dict(make_smooth=True)
 54 | 
 55 | 
 56 | class ComplexHomotopy(Homotopy):
 57 |     def __init__(
 58 |         self,
 59 |         complex_homotopy: Callable[[complex, float], complex],
 60 |         mobject: Mobject,
 61 |         **kwargs
 62 |     ):
 63 |         """
 64 |         Given a function form (z, t) -> w, where z and w
 65 |         are complex numbers and t is time, this animates
 66 |         the state over time
 67 |         """
 68 |         def homotopy(x, y, z, t):
 69 |             c = complex_homotopy(complex(x, y), t)
 70 |             return (c.real, c.imag, z)
 71 | 
 72 |         super().__init__(homotopy, mobject, **kwargs)
 73 | 
 74 | 
 75 | class PhaseFlow(Animation):
 76 |     def __init__(
 77 |         self,
 78 |         function: Callable[[np.ndarray], np.ndarray],
 79 |         mobject: Mobject,
 80 |         virtual_time: float | None = None,
 81 |         suspend_mobject_updating: bool = False,
 82 |         rate_func: Callable[[float], float] = linear,
 83 |         run_time: float =3.0,
 84 |         **kwargs
 85 |     ):
 86 |         self.function = function
 87 |         self.virtual_time = virtual_time or run_time
 88 |         super().__init__(
 89 |             mobject,
 90 |             rate_func=rate_func,
 91 |             run_time=run_time,
 92 |             suspend_mobject_updating=suspend_mobject_updating,
 93 |             **kwargs
 94 |         )
 95 | 
 96 |     def interpolate_mobject(self, alpha: float) -> None:
 97 |         if hasattr(self, "last_alpha"):
 98 |             dt = self.virtual_time * (alpha - self.last_alpha)
 99 |             self.mobject.apply_function(
100 |                 lambda p: p + dt * self.function(p)
101 |             )
102 |         self.last_alpha = alpha
103 | 
104 | 
105 | class MoveAlongPath(Animation):
106 |     def __init__(
107 |         self,
108 |         mobject: Mobject,
109 |         path: VMobject,
110 |         suspend_mobject_updating: bool = False,
111 |         **kwargs
112 |     ):
113 |         self.path = path
114 |         super().__init__(mobject, suspend_mobject_updating=suspend_mobject_updating, **kwargs)
115 | 
116 |     def interpolate_mobject(self, alpha: float) -> None:
117 |         point = self.path.quick_point_from_proportion(self.rate_func(alpha))
118 |         self.mobject.move_to(point)
119 | 


--------------------------------------------------------------------------------
/manimlib/animation/numbers.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from manimlib.animation.animation import Animation
 4 | from manimlib.mobject.numbers import DecimalNumber
 5 | from manimlib.utils.bezier import interpolate
 6 | from manimlib.utils.simple_functions import clip
 7 | 
 8 | from typing import TYPE_CHECKING
 9 | 
10 | if TYPE_CHECKING:
11 |     from typing import Callable
12 | 
13 | 
14 | class ChangingDecimal(Animation):
15 |     def __init__(
16 |         self,
17 |         decimal_mob: DecimalNumber,
18 |         number_update_func: Callable[[float], float],
19 |         suspend_mobject_updating: bool = False,
20 |         **kwargs
21 |     ):
22 |         assert isinstance(decimal_mob, DecimalNumber)
23 |         self.number_update_func = number_update_func
24 |         super().__init__(
25 |             decimal_mob,
26 |             suspend_mobject_updating=suspend_mobject_updating,
27 |             **kwargs
28 |         )
29 |         self.mobject = decimal_mob
30 | 
31 |     def interpolate_mobject(self, alpha: float) -> None:
32 |         true_alpha = self.time_spanned_alpha(alpha)
33 |         new_value = self.number_update_func(true_alpha)
34 |         self.mobject.set_value(new_value)
35 | 
36 | 
37 | class ChangeDecimalToValue(ChangingDecimal):
38 |     def __init__(
39 |         self,
40 |         decimal_mob: DecimalNumber,
41 |         target_number: float | complex,
42 |         **kwargs
43 |     ):
44 |         start_number = decimal_mob.number
45 |         super().__init__(
46 |             decimal_mob,
47 |             lambda a: interpolate(start_number, target_number, a),
48 |             **kwargs
49 |         )
50 | 
51 | 
52 | class CountInFrom(ChangingDecimal):
53 |     def __init__(
54 |         self,
55 |         decimal_mob: DecimalNumber,
56 |         source_number: float | complex = 0,
57 |         **kwargs
58 |     ):
59 |         start_number = decimal_mob.get_value()
60 |         super().__init__(
61 |             decimal_mob,
62 |             lambda a: interpolate(source_number, start_number, clip(a, 0, 1)),
63 |             **kwargs
64 |         )
65 | 


--------------------------------------------------------------------------------
/manimlib/animation/rotation.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from manimlib.animation.animation import Animation
 4 | from manimlib.constants import ORIGIN, OUT
 5 | from manimlib.constants import PI, TAU
 6 | from manimlib.utils.rate_functions import linear
 7 | from manimlib.utils.rate_functions import smooth
 8 | 
 9 | from typing import TYPE_CHECKING
10 | 
11 | if TYPE_CHECKING:
12 |     import numpy as np
13 |     from typing import Callable
14 |     from manimlib.mobject.mobject import Mobject
15 | 
16 | 
17 | class Rotating(Animation):
18 |     def __init__(
19 |         self,
20 |         mobject: Mobject,
21 |         angle: float = TAU,
22 |         axis: np.ndarray = OUT,
23 |         about_point: np.ndarray | None = None,
24 |         about_edge: np.ndarray | None = None,
25 |         run_time: float = 5.0,
26 |         rate_func: Callable[[float], float] = linear,
27 |         suspend_mobject_updating: bool = False,
28 |         **kwargs
29 |     ):
30 |         self.angle = angle
31 |         self.axis = axis
32 |         self.about_point = about_point
33 |         self.about_edge = about_edge
34 |         super().__init__(
35 |             mobject,
36 |             run_time=run_time,
37 |             rate_func=rate_func,
38 |             suspend_mobject_updating=suspend_mobject_updating,
39 |             **kwargs
40 |         )
41 | 
42 |     def interpolate_mobject(self, alpha: float) -> None:
43 |         pairs = zip(
44 |             self.mobject.family_members_with_points(),
45 |             self.starting_mobject.family_members_with_points(),
46 |         )
47 |         for sm1, sm2 in pairs:
48 |             for key in sm1.pointlike_data_keys:
49 |                 sm1.data[key][:] = sm2.data[key]
50 |         self.mobject.rotate(
51 |             self.rate_func(self.time_spanned_alpha(alpha)) * self.angle,
52 |             axis=self.axis,
53 |             about_point=self.about_point,
54 |             about_edge=self.about_edge,
55 |         )
56 | 
57 | 
58 | class Rotate(Rotating):
59 |     def __init__(
60 |         self,
61 |         mobject: Mobject,
62 |         angle: float = PI,
63 |         axis: np.ndarray = OUT,
64 |         run_time: float = 1,
65 |         rate_func: Callable[[float], float] = smooth,
66 |         about_edge: np.ndarray = ORIGIN,
67 |         **kwargs
68 |     ):
69 |         super().__init__(
70 |             mobject, angle, axis,
71 |             run_time=run_time,
72 |             rate_func=rate_func,
73 |             about_edge=about_edge,
74 |             **kwargs
75 |         )
76 | 


--------------------------------------------------------------------------------
/manimlib/animation/specialized.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from manimlib.animation.composition import LaggedStart
 4 | from manimlib.animation.transform import Restore
 5 | from manimlib.constants import BLACK, WHITE
 6 | from manimlib.mobject.geometry import Circle
 7 | from manimlib.mobject.types.vectorized_mobject import VGroup
 8 | 
 9 | from typing import TYPE_CHECKING
10 | 
11 | if TYPE_CHECKING:
12 |     import numpy as np
13 |     from manimlib.typing import ManimColor
14 | 
15 | 
16 | class Broadcast(LaggedStart):
17 |     def __init__(
18 |         self,
19 |         focal_point: np.ndarray,
20 |         small_radius: float = 0.0,
21 |         big_radius: float = 5.0,
22 |         n_circles: int = 5,
23 |         start_stroke_width: float = 8.0,
24 |         color: ManimColor = WHITE,
25 |         run_time: float = 3.0,
26 |         lag_ratio: float = 0.2,
27 |         remover: bool = True,
28 |         **kwargs
29 |     ):
30 |         self.focal_point = focal_point
31 |         self.small_radius = small_radius
32 |         self.big_radius = big_radius
33 |         self.n_circles = n_circles
34 |         self.start_stroke_width = start_stroke_width
35 |         self.color = color
36 | 
37 |         circles = VGroup()
38 |         for x in range(n_circles):
39 |             circle = Circle(
40 |                 radius=big_radius,
41 |                 stroke_color=BLACK,
42 |                 stroke_width=0,
43 |             )
44 |             circle.add_updater(lambda c: c.move_to(focal_point))
45 |             circle.save_state()
46 |             circle.set_width(small_radius * 2)
47 |             circle.set_stroke(color, start_stroke_width)
48 |             circles.add(circle)
49 |         super().__init__(
50 |             *map(Restore, circles),
51 |             run_time=run_time,
52 |             lag_ratio=lag_ratio,
53 |             remover=remover,
54 |             **kwargs
55 |         )
56 | 


--------------------------------------------------------------------------------
/manimlib/animation/update.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from manimlib.animation.animation import Animation
 4 | 
 5 | from typing import TYPE_CHECKING
 6 | 
 7 | if TYPE_CHECKING:
 8 |     from typing import Callable
 9 | 
10 |     from manimlib.mobject.mobject import Mobject
11 | 
12 | 
13 | class UpdateFromFunc(Animation):
14 |     """
15 |     update_function of the form func(mobject), presumably
16 |     to be used when the state of one mobject is dependent
17 |     on another simultaneously animated mobject
18 |     """
19 |     def __init__(
20 |         self,
21 |         mobject: Mobject,
22 |         update_function: Callable[[Mobject], Mobject | None],
23 |         suspend_mobject_updating: bool = False,
24 |         **kwargs
25 |     ):
26 |         self.update_function = update_function
27 |         super().__init__(
28 |             mobject,
29 |             suspend_mobject_updating=suspend_mobject_updating,
30 |             **kwargs
31 |         )
32 | 
33 |     def interpolate_mobject(self, alpha: float) -> None:
34 |         self.update_function(self.mobject)
35 | 
36 | 
37 | class UpdateFromAlphaFunc(Animation):
38 |     def __init__(
39 |         self,
40 |         mobject: Mobject,
41 |         update_function: Callable[[Mobject, float], Mobject | None],
42 |         suspend_mobject_updating: bool = False,
43 |         **kwargs
44 |     ):
45 |         self.update_function = update_function
46 |         super().__init__(mobject, suspend_mobject_updating=suspend_mobject_updating, **kwargs)
47 | 
48 |     def interpolate_mobject(self, alpha: float) -> None:
49 |         self.update_function(self.mobject, alpha)
50 | 
51 | 
52 | class MaintainPositionRelativeTo(Animation):
53 |     def __init__(
54 |         self,
55 |         mobject: Mobject,
56 |         tracked_mobject: Mobject,
57 |         **kwargs
58 |     ):
59 |         self.tracked_mobject = tracked_mobject
60 |         self.diff = mobject.get_center() - tracked_mobject.get_center()
61 |         super().__init__(mobject, **kwargs)
62 | 
63 |     def interpolate_mobject(self, alpha: float) -> None:
64 |         target = self.tracked_mobject.get_center()
65 |         location = self.mobject.get_center()
66 |         self.mobject.shift(target - location + self.diff)
67 | 


--------------------------------------------------------------------------------
/manimlib/camera/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3b1b/manim/41613db7eca9eabbb40aa830631e5206c9282f0f/manimlib/camera/__init__.py


--------------------------------------------------------------------------------
/manimlib/constants.py:
--------------------------------------------------------------------------------
  1 | from __future__ import annotations
  2 | import numpy as np
  3 | 
  4 | from typing import TYPE_CHECKING
  5 | if TYPE_CHECKING:
  6 |     from typing import List
  7 |     from manimlib.typing import ManimColor, Vect3
  8 | 
  9 | # See manimlib/default_config.yml
 10 | from manimlib.config import manim_config
 11 | 
 12 | 
 13 | DEFAULT_RESOLUTION: tuple[int, int] = manim_config.camera.resolution
 14 | DEFAULT_PIXEL_WIDTH: int = DEFAULT_RESOLUTION[0]
 15 | DEFAULT_PIXEL_HEIGHT: int = DEFAULT_RESOLUTION[1]
 16 | 
 17 | # Sizes relevant to default camera frame
 18 | ASPECT_RATIO: float = DEFAULT_PIXEL_WIDTH / DEFAULT_PIXEL_HEIGHT
 19 | FRAME_HEIGHT: float = manim_config.sizes.frame_height
 20 | FRAME_WIDTH: float = FRAME_HEIGHT * ASPECT_RATIO
 21 | FRAME_SHAPE: tuple[float, float] = (FRAME_WIDTH, FRAME_HEIGHT)
 22 | FRAME_Y_RADIUS: float = FRAME_HEIGHT / 2
 23 | FRAME_X_RADIUS: float = FRAME_WIDTH / 2
 24 | 
 25 | 
 26 | # Helpful values for positioning mobjects
 27 | SMALL_BUFF: float = manim_config.sizes.small_buff
 28 | MED_SMALL_BUFF: float = manim_config.sizes.med_small_buff
 29 | MED_LARGE_BUFF: float = manim_config.sizes.med_large_buff
 30 | LARGE_BUFF: float = manim_config.sizes.large_buff
 31 | 
 32 | DEFAULT_MOBJECT_TO_EDGE_BUFF: float = manim_config.sizes.default_mobject_to_edge_buff
 33 | DEFAULT_MOBJECT_TO_MOBJECT_BUFF: float = manim_config.sizes.default_mobject_to_mobject_buff
 34 | 
 35 | 
 36 | # Standard vectors
 37 | ORIGIN: Vect3 = np.array([0., 0., 0.])
 38 | UP: Vect3 = np.array([0., 1., 0.])
 39 | DOWN: Vect3 = np.array([0., -1., 0.])
 40 | RIGHT: Vect3 = np.array([1., 0., 0.])
 41 | LEFT: Vect3 = np.array([-1., 0., 0.])
 42 | IN: Vect3 = np.array([0., 0., -1.])
 43 | OUT: Vect3 = np.array([0., 0., 1.])
 44 | X_AXIS: Vect3 = np.array([1., 0., 0.])
 45 | Y_AXIS: Vect3 = np.array([0., 1., 0.])
 46 | Z_AXIS: Vect3 = np.array([0., 0., 1.])
 47 | 
 48 | NULL_POINTS = np.array([[0., 0., 0.]])
 49 | 
 50 | # Useful abbreviations for diagonals
 51 | UL: Vect3 = UP + LEFT
 52 | UR: Vect3 = UP + RIGHT
 53 | DL: Vect3 = DOWN + LEFT
 54 | DR: Vect3 = DOWN + RIGHT
 55 | 
 56 | TOP: Vect3 = FRAME_Y_RADIUS * UP
 57 | BOTTOM: Vect3 = FRAME_Y_RADIUS * DOWN
 58 | LEFT_SIDE: Vect3 = FRAME_X_RADIUS * LEFT
 59 | RIGHT_SIDE: Vect3 = FRAME_X_RADIUS * RIGHT
 60 | 
 61 | # Angles
 62 | PI: float = np.pi
 63 | TAU: float = 2 * PI
 64 | DEG: float = TAU / 360
 65 | DEGREES = DEG  # Many older animations use the full name
 66 | # Nice to have a constant for readability
 67 | # when juxtaposed with expressions like 30 * DEG
 68 | RADIANS: float = 1
 69 | 
 70 | # Related to Text
 71 | NORMAL: str = "NORMAL"
 72 | ITALIC: str = "ITALIC"
 73 | OBLIQUE: str = "OBLIQUE"
 74 | BOLD: str = "BOLD"
 75 | 
 76 | DEFAULT_STROKE_WIDTH: float = manim_config.vmobject.default_stroke_width
 77 | 
 78 | # Colors
 79 | BLUE_E: ManimColor = manim_config.colors.blue_e
 80 | BLUE_D: ManimColor = manim_config.colors.blue_d
 81 | BLUE_C: ManimColor = manim_config.colors.blue_c
 82 | BLUE_B: ManimColor = manim_config.colors.blue_b
 83 | BLUE_A: ManimColor = manim_config.colors.blue_a
 84 | TEAL_E: ManimColor = manim_config.colors.teal_e
 85 | TEAL_D: ManimColor = manim_config.colors.teal_d
 86 | TEAL_C: ManimColor = manim_config.colors.teal_c
 87 | TEAL_B: ManimColor = manim_config.colors.teal_b
 88 | TEAL_A: ManimColor = manim_config.colors.teal_a
 89 | GREEN_E: ManimColor = manim_config.colors.green_e
 90 | GREEN_D: ManimColor = manim_config.colors.green_d
 91 | GREEN_C: ManimColor = manim_config.colors.green_c
 92 | GREEN_B: ManimColor = manim_config.colors.green_b
 93 | GREEN_A: ManimColor = manim_config.colors.green_a
 94 | YELLOW_E: ManimColor = manim_config.colors.yellow_e
 95 | YELLOW_D: ManimColor = manim_config.colors.yellow_d
 96 | YELLOW_C: ManimColor = manim_config.colors.yellow_c
 97 | YELLOW_B: ManimColor = manim_config.colors.yellow_b
 98 | YELLOW_A: ManimColor = manim_config.colors.yellow_a
 99 | GOLD_E: ManimColor = manim_config.colors.gold_e
100 | GOLD_D: ManimColor = manim_config.colors.gold_d
101 | GOLD_C: ManimColor = manim_config.colors.gold_c
102 | GOLD_B: ManimColor = manim_config.colors.gold_b
103 | GOLD_A: ManimColor = manim_config.colors.gold_a
104 | RED_E: ManimColor = manim_config.colors.red_e
105 | RED_D: ManimColor = manim_config.colors.red_d
106 | RED_C: ManimColor = manim_config.colors.red_c
107 | RED_B: ManimColor = manim_config.colors.red_b
108 | RED_A: ManimColor = manim_config.colors.red_a
109 | MAROON_E: ManimColor = manim_config.colors.maroon_e
110 | MAROON_D: ManimColor = manim_config.colors.maroon_d
111 | MAROON_C: ManimColor = manim_config.colors.maroon_c
112 | MAROON_B: ManimColor = manim_config.colors.maroon_b
113 | MAROON_A: ManimColor = manim_config.colors.maroon_a
114 | PURPLE_E: ManimColor = manim_config.colors.purple_e
115 | PURPLE_D: ManimColor = manim_config.colors.purple_d
116 | PURPLE_C: ManimColor = manim_config.colors.purple_c
117 | PURPLE_B: ManimColor = manim_config.colors.purple_b
118 | PURPLE_A: ManimColor = manim_config.colors.purple_a
119 | GREY_E: ManimColor = manim_config.colors.grey_e
120 | GREY_D: ManimColor = manim_config.colors.grey_d
121 | GREY_C: ManimColor = manim_config.colors.grey_c
122 | GREY_B: ManimColor = manim_config.colors.grey_b
123 | GREY_A: ManimColor = manim_config.colors.grey_a
124 | WHITE: ManimColor = manim_config.colors.white
125 | BLACK: ManimColor = manim_config.colors.black
126 | GREY_BROWN: ManimColor = manim_config.colors.grey_brown
127 | DARK_BROWN: ManimColor = manim_config.colors.dark_brown
128 | LIGHT_BROWN: ManimColor = manim_config.colors.light_brown
129 | PINK: ManimColor = manim_config.colors.pink
130 | LIGHT_PINK: ManimColor = manim_config.colors.light_pink
131 | GREEN_SCREEN: ManimColor = manim_config.colors.green_screen
132 | ORANGE: ManimColor = manim_config.colors.orange
133 | PURE_RED: ManimColor = manim_config.colors.pure_red
134 | PURE_GREEN: ManimColor = manim_config.colors.pure_green
135 | PURE_BLUE: ManimColor = manim_config.colors.pure_blue
136 | 
137 | MANIM_COLORS: List[ManimColor] = list(manim_config.colors.values())
138 | 
139 | # Abbreviated names for the "median" colors
140 | BLUE: ManimColor = BLUE_C
141 | TEAL: ManimColor = TEAL_C
142 | GREEN: ManimColor = GREEN_C
143 | YELLOW: ManimColor = YELLOW_C
144 | GOLD: ManimColor = GOLD_C
145 | RED: ManimColor = RED_C
146 | MAROON: ManimColor = MAROON_C
147 | PURPLE: ManimColor = PURPLE_C
148 | GREY: ManimColor = GREY_C
149 | 
150 | COLORMAP_3B1B: List[ManimColor] = [BLUE_E, GREEN, YELLOW, RED]
151 | 
152 | # Default mobject colors should be configurable just like background color
153 | # DEFAULT_MOBJECT_COLOR is mainly for text, tex, line, etc... mobjects. Default is WHITE
154 | # DEFAULT_LIGHT_COLOR is mainly for things like axes, arrows, annulus and other lightly colored mobjects. Default is GREY_B
155 | DEFAULT_MOBJECT_COLOR: ManimColor = manim_config.mobject.default_mobject_color or WHITE
156 | DEFAULT_LIGHT_COLOR: ManimColor = manim_config.mobject.default_light_color or GREY_B
157 | 
158 | DEFAULT_VMOBJECT_STROKE_COLOR : ManimColor = manim_config.vmobject.default_stroke_color or GREY_A
159 | DEFAULT_VMOBJECT_FILL_COLOR : ManimColor = manim_config.vmobject.default_fill_color or GREY_C
160 | 


--------------------------------------------------------------------------------
/manimlib/default_config.yml:
--------------------------------------------------------------------------------
  1 | # This file determines the default configuration for how manim is
  2 | # run, including names for directories it will write to, default
  3 | # parameters for various classes, style choices, etc. To customize
  4 | # your own, create a custom_config.yml file in whatever directory
  5 | # you are running manim. For 3blue1brown, for instance, mind is
  6 | # here: https://github.com/3b1b/videos/blob/master/custom_config.yml
  7 | 
  8 | # Alternatively, you can create it wherever you like, and on running
  9 | # manim, pass in `--config_file /path/to/custom/config/file.yml`
 10 | 
 11 | directories:
 12 |   # Set this to true if you want the path to video files
 13 |   # to match the directory structure of the path to the
 14 |   # source code generating that video
 15 |   mirror_module_path: False
 16 |   # Manim may write to and read from the file system, e.g.
 17 |   # to render videos and to look for svg/png assets. This
 18 |   # will specify where those assets live, with a base directory,
 19 |   # and various subdirectory names within it
 20 |   base: ""
 21 |   subdirs:
 22 |     # Where should manim output video and image files?
 23 |     output: "videos"
 24 |     # If you want to use images, manim will look to these folders to find them
 25 |     raster_images: "raster_images"
 26 |     vector_images: "vector_images"
 27 |     # If you want to use sounds, manim will look here to find it.
 28 |     sounds: "sounds"
 29 |     # Place for other forms of data relevant to any projects, like csv's
 30 |     data: "data"
 31 |     # When downloading, say an image, where will it go?
 32 |     downloads: "downloads"
 33 |   # For certain object types, especially Tex and Text, manim will save information
 34 |   # to file to prevent the need to re-compute, e.g. recompiling the latex. By default,
 35 |   # it stores this saved data to whatever directory appdirs.user_cache_dir("manim") returns,
 36 |   # but here a user can specify a different cache location
 37 |   cache: ""
 38 | window:
 39 |   # The position of window on screen. UR -> Upper Right, and likewise DL -> Down and Left,
 40 |   # UO would be upper middle, etc.
 41 |   position_string: UR
 42 |   # If using multiple monitors, which one should show the window
 43 |   monitor_index: 0
 44 |   # If not full screen, the default to give it half the screen width
 45 |   full_screen: False
 46 |   # Other optional specifications that override the above include:
 47 |   # position: (500, 500)  # Specific position, in pixel coordinates, for upper right corner
 48 |   # size: (1920, 1080)  # Specific size, in pixels
 49 | camera:
 50 |   resolution: (1920, 1080)
 51 |   background_color: "#333333"
 52 |   fps: 30
 53 |   background_opacity: 1.0
 54 | file_writer:
 55 |   # What command to use for ffmpeg
 56 |   ffmpeg_bin: "ffmpeg"
 57 |   # Parameters to pass into ffmpeg
 58 |   video_codec: "libx264"
 59 |   pixel_format: "yuv420p"
 60 |   saturation: 1.0
 61 |   gamma: 1.0
 62 | # Most of the scene configuration will come from CLI arguments,
 63 | # but defaults can be set here
 64 | scene:
 65 |   show_animation_progress: False
 66 |   leave_progress_bars: False
 67 |   # When skipping animations, should a single frame be rendered
 68 |   # at the end of each play call?
 69 |   preview_while_skipping: True
 70 |   # How long does a scene pause on Scene.wait calls
 71 |   default_wait_time: 1.0
 72 | vmobject:
 73 |   default_stroke_width: 4.0
 74 |   default_stroke_color: "#DDDDDD"     # Default is GREY_A
 75 |   default_fill_color: "#888888"       # Default is GREY_C
 76 | mobject:
 77 |   default_mobject_color: "#FFFFFF"    # Default is WHITE
 78 |   default_light_color: "#BBBBBB"      # Default is GREY_B
 79 | tex:
 80 |   # See tex_templates.yml
 81 |   template: "default"
 82 | text:
 83 |   # font: "Cambria Math"
 84 |   font: "Consolas"
 85 |   alignment: "LEFT"
 86 | embed:
 87 |   exception_mode: "Verbose"
 88 |   autoreload: False
 89 | resolution_options:
 90 |   # When the user passes in -l, -m, --hd or --uhd, these are the corresponding
 91 |   # resolutions
 92 |   low: (854, 480)
 93 |   med: (1280, 720)
 94 |   high: (1920, 1080)
 95 |   4k: (3840, 2160)
 96 | sizes:
 97 |   # This determines the scale of the manim coordinate system with respect to
 98 |   # the viewing frame
 99 |   frame_height: 8.0
100 |   # These determine the constants SMALL_BUFF, MED_SMALL_BUFF, etc., useful
101 |   # for nudging things around and having default spacing values
102 |   small_buff: 0.1
103 |   med_small_buff: 0.25
104 |   med_large_buff: 0.5
105 |   large_buff: 1.0
106 |   # Default buffers used in Mobject.next_to or Mobject.to_edge
107 |   default_mobject_to_edge_buff: 0.5
108 |   default_mobject_to_mobject_buff: 0.25
109 | key_bindings:
110 |   pan_3d: "d"
111 |   pan: "f"
112 |   reset: "r"
113 |   quit: "q" # Together with command
114 |   select: "s"
115 |   unselect: "u"
116 |   grab: "g"
117 |   x_grab: "h"
118 |   y_grab: "v"
119 |   resize: "t"
120 |   color: "c"
121 |   information: "i"
122 |   cursor: "k"
123 | colors:
124 |   blue_e: "#1C758A"
125 |   blue_d: "#29ABCA"
126 |   blue_c: "#58C4DD"
127 |   blue_b: "#9CDCEB"
128 |   blue_a: "#C7E9F1"
129 |   teal_e: "#49A88F"
130 |   teal_d: "#55C1A7"
131 |   teal_c: "#5CD0B3"
132 |   teal_b: "#76DDC0"
133 |   teal_a: "#ACEAD7"
134 |   green_e: "#699C52"
135 |   green_d: "#77B05D"
136 |   green_c: "#83C167"
137 |   green_b: "#A6CF8C"
138 |   green_a: "#C9E2AE"
139 |   yellow_e: "#E8C11C"
140 |   yellow_d: "#F4D345"
141 |   yellow_c: "#FFFF00"
142 |   yellow_b: "#FFEA94"
143 |   yellow_a: "#FFF1B6"
144 |   gold_e: "#C78D46"
145 |   gold_d: "#E1A158"
146 |   gold_c: "#F0AC5F"
147 |   gold_b: "#F9B775"
148 |   gold_a: "#F7C797"
149 |   red_e: "#CF5044"
150 |   red_d: "#E65A4C"
151 |   red_c: "#FC6255"
152 |   red_b: "#FF8080"
153 |   red_a: "#F7A1A3"
154 |   maroon_e: "#94424F"
155 |   maroon_d: "#A24D61"
156 |   maroon_c: "#C55F73"
157 |   maroon_b: "#EC92AB"
158 |   maroon_a: "#ECABC1"
159 |   purple_e: "#644172"
160 |   purple_d: "#715582"
161 |   purple_c: "#9A72AC"
162 |   purple_b: "#B189C6"
163 |   purple_a: "#CAA3E8"
164 |   grey_e: "#222222"
165 |   grey_d: "#444444"
166 |   grey_c: "#888888"
167 |   grey_b: "#BBBBBB"
168 |   grey_a: "#DDDDDD"
169 |   white: "#FFFFFF"
170 |   black: "#000000"
171 |   grey_brown: "#736357"
172 |   dark_brown: "#8B4513"
173 |   light_brown: "#CD853F"
174 |   pink: "#D147BD"
175 |   light_pink: "#DC75CD"
176 |   green_screen: "#00FF00"
177 |   orange: "#FF862F"
178 |   pure_red: "#FF0000"
179 |   pure_green: "#00FF00"
180 |   pure_blue: "#0000FF"
181 | # Can be DEBUG / INFO / WARNING / ERROR / CRITICAL
182 | log_level: "INFO"
183 | universal_import_line: "from manimlib import *"
184 | ignore_manimlib_modules_on_reload: True
185 | 


--------------------------------------------------------------------------------
/manimlib/event_handler/__init__.py:
--------------------------------------------------------------------------------
1 | from manimlib.event_handler.event_dispatcher import EventDispatcher
2 | 
3 | 
4 | # This is supposed to be a Singleton
5 | # i.e., during runtime there should be only one object of Event Dispatcher
6 | EVENT_DISPATCHER = EventDispatcher()
7 | 


--------------------------------------------------------------------------------
/manimlib/event_handler/event_dispatcher.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import numpy as np
 4 | 
 5 | from manimlib.event_handler.event_listner import EventListener
 6 | from manimlib.event_handler.event_type import EventType
 7 | 
 8 | 
 9 | class EventDispatcher(object):
10 |     def __init__(self):
11 |         self.event_listners: dict[
12 |             EventType, list[EventListener]
13 |         ] = {
14 |             event_type: []
15 |             for event_type in EventType
16 |         }
17 |         self.mouse_point = np.array((0., 0., 0.))
18 |         self.mouse_drag_point = np.array((0., 0., 0.))
19 |         self.pressed_keys: set[int] = set()
20 |         self.draggable_object_listners: list[EventListener] = []
21 | 
22 |     def add_listner(self, event_listner: EventListener):
23 |         assert isinstance(event_listner, EventListener)
24 |         self.event_listners[event_listner.event_type].append(event_listner)
25 |         return self
26 | 
27 |     def remove_listner(self, event_listner: EventListener):
28 |         assert isinstance(event_listner, EventListener)
29 |         try:
30 |             while event_listner in self.event_listners[event_listner.event_type]:
31 |                 self.event_listners[event_listner.event_type].remove(event_listner)
32 |         except:
33 |             # raise ValueError("Handler is not handling this event, so cannot remove it.")
34 |             pass
35 |         return self
36 | 
37 |     def dispatch(self, event_type: EventType, **event_data):
38 |         if event_type == EventType.MouseMotionEvent:
39 |             self.mouse_point = event_data["point"]
40 |         elif event_type == EventType.MouseDragEvent:
41 |             self.mouse_drag_point = event_data["point"]
42 |         elif event_type == EventType.KeyPressEvent:
43 |             self.pressed_keys.add(event_data["symbol"])  # Modifiers?
44 |         elif event_type == EventType.KeyReleaseEvent:
45 |             self.pressed_keys.difference_update({event_data["symbol"]})  # Modifiers?
46 |         elif event_type == EventType.MousePressEvent:
47 |             self.draggable_object_listners = [
48 |                 listner
49 |                 for listner in self.event_listners[EventType.MouseDragEvent]
50 |                 if listner.mobject.is_point_touching(self.mouse_point)
51 |             ]
52 |         elif event_type == EventType.MouseReleaseEvent:
53 |             self.draggable_object_listners = []
54 | 
55 |         propagate_event = None
56 | 
57 |         if event_type == EventType.MouseDragEvent:
58 |             for listner in self.draggable_object_listners:
59 |                 assert isinstance(listner, EventListener)
60 |                 propagate_event = listner.callback(listner.mobject, event_data)
61 |                 if propagate_event is not None and propagate_event is False:
62 |                     return propagate_event
63 | 
64 |         elif event_type.value.startswith('mouse'):
65 |             for listner in self.event_listners[event_type]:
66 |                 if listner.mobject.is_point_touching(self.mouse_point):
67 |                     propagate_event = listner.callback(
68 |                         listner.mobject, event_data)
69 |                     if propagate_event is not None and propagate_event is False:
70 |                         return propagate_event
71 | 
72 |         elif event_type.value.startswith('key'):
73 |             for listner in self.event_listners[event_type]:
74 |                 propagate_event = listner.callback(listner.mobject, event_data)
75 |                 if propagate_event is not None and propagate_event is False:
76 |                     return propagate_event
77 | 
78 |         return propagate_event
79 | 
80 |     def get_listners_count(self) -> int:
81 |         return sum([len(value) for key, value in self.event_listners.items()])
82 | 
83 |     def get_mouse_point(self) -> np.ndarray:
84 |         return self.mouse_point
85 | 
86 |     def get_mouse_drag_point(self) -> np.ndarray:
87 |         return self.mouse_drag_point
88 | 
89 |     def is_key_pressed(self, symbol: int) -> bool:
90 |         return (symbol in self.pressed_keys)
91 | 
92 |     __iadd__ = add_listner
93 |     __isub__ = remove_listner
94 |     __call__ = dispatch
95 |     __len__ = get_listners_count
96 | 


--------------------------------------------------------------------------------
/manimlib/event_handler/event_listner.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from typing import TYPE_CHECKING
 4 | 
 5 | if TYPE_CHECKING:
 6 |     from typing import Callable
 7 | 
 8 |     from manimlib.event_handler.event_type import EventType
 9 |     from manimlib.mobject.mobject import Mobject
10 | 
11 | 
12 | class EventListener(object):
13 |     def __init__(
14 |         self,
15 |         mobject: Mobject,
16 |         event_type: EventType,
17 |         event_callback: Callable[[Mobject, dict[str]]]
18 |     ):
19 |         self.mobject = mobject
20 |         self.event_type = event_type
21 |         self.callback = event_callback
22 | 
23 |     def __eq__(self, o: object) -> bool:
24 |         return_val = False
25 |         try:
26 |             return_val = self.callback == o.callback \
27 |                 and self.mobject == o.mobject \
28 |                 and self.event_type == o.event_type
29 |         except:
30 |             pass
31 |         return return_val
32 | 


--------------------------------------------------------------------------------
/manimlib/event_handler/event_type.py:
--------------------------------------------------------------------------------
 1 | from enum import Enum
 2 | 
 3 | 
 4 | class EventType(Enum):
 5 |     MouseMotionEvent = 'mouse_motion_event'
 6 |     MousePressEvent = 'mouse_press_event'
 7 |     MouseReleaseEvent = 'mouse_release_event'
 8 |     MouseDragEvent = 'mouse_drag_event'
 9 |     MouseScrollEvent = 'mouse_scroll_event'
10 |     KeyPressEvent = 'key_press_event'
11 |     KeyReleaseEvent = 'key_release_event'
12 | 


--------------------------------------------------------------------------------
/manimlib/logger.py:
--------------------------------------------------------------------------------
 1 | import logging
 2 | 
 3 | from rich.logging import RichHandler
 4 | 
 5 | __all__ = ["log"]
 6 | 
 7 | 
 8 | FORMAT = "%(message)s"
 9 | logging.basicConfig(
10 |     level=logging.WARNING, format=FORMAT, datefmt="[%X]", handlers=[RichHandler()]
11 | )
12 | 
13 | log = logging.getLogger("manimgl")
14 | 


--------------------------------------------------------------------------------
/manimlib/mobject/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3b1b/manim/41613db7eca9eabbb40aa830631e5206c9282f0f/manimlib/mobject/__init__.py


--------------------------------------------------------------------------------
/manimlib/mobject/boolean_ops.py:
--------------------------------------------------------------------------------
  1 | from __future__ import annotations
  2 | 
  3 | import numpy as np
  4 | import pathops
  5 | 
  6 | from manimlib.mobject.types.vectorized_mobject import VMobject
  7 | 
  8 | 
  9 | # Boolean operations between 2D mobjects
 10 | # Borrowed from https://github.com/ManimCommunity/manim/
 11 | 
 12 | def _convert_vmobject_to_skia_path(vmobject: VMobject) -> pathops.Path:
 13 |     path = pathops.Path()
 14 |     for submob in vmobject.family_members_with_points():
 15 |         for subpath in submob.get_subpaths():
 16 |             quads = vmobject.get_bezier_tuples_from_points(subpath)
 17 |             start = subpath[0]
 18 |             path.moveTo(*start[:2])
 19 |             for p0, p1, p2 in quads:
 20 |                 path.quadTo(*p1[:2], *p2[:2])
 21 |             if vmobject.consider_points_equal(subpath[0], subpath[-1]):
 22 |                 path.close()
 23 |     return path
 24 | 
 25 | 
 26 | def _convert_skia_path_to_vmobject(
 27 |     path: pathops.Path,
 28 |     vmobject: VMobject
 29 | ) -> VMobject:
 30 |     PathVerb = pathops.PathVerb
 31 |     current_path_start = np.array([0.0, 0.0, 0.0])
 32 |     for path_verb, points in path:
 33 |         if path_verb == PathVerb.CLOSE:
 34 |             vmobject.add_line_to(current_path_start)
 35 |         else:
 36 |             points = np.hstack((np.array(points), np.zeros((len(points), 1))))
 37 |             if path_verb == PathVerb.MOVE:
 38 |                 for point in points:
 39 |                     current_path_start = point
 40 |                     vmobject.start_new_path(point)
 41 |             elif path_verb == PathVerb.CUBIC:
 42 |                 vmobject.add_cubic_bezier_curve_to(*points)
 43 |             elif path_verb == PathVerb.LINE:
 44 |                 vmobject.add_line_to(points[0])
 45 |             elif path_verb == PathVerb.QUAD:
 46 |                 vmobject.add_quadratic_bezier_curve_to(*points)
 47 |             else:
 48 |                 raise Exception(f"Unsupported: {path_verb}")
 49 |     return vmobject.reverse_points()
 50 | 
 51 | 
 52 | class Union(VMobject):
 53 |     def __init__(self, *vmobjects: VMobject, **kwargs):
 54 |         if len(vmobjects) < 2:
 55 |             raise ValueError("At least 2 mobjects needed for Union.")
 56 |         super().__init__(**kwargs)
 57 |         outpen = pathops.Path()
 58 |         paths = [
 59 |             _convert_vmobject_to_skia_path(vmobject)
 60 |             for vmobject in vmobjects
 61 |         ]
 62 |         pathops.union(paths, outpen.getPen())
 63 |         _convert_skia_path_to_vmobject(outpen, self)
 64 | 
 65 | 
 66 | class Difference(VMobject):
 67 |     def __init__(self, subject: VMobject, clip: VMobject, **kwargs):
 68 |         super().__init__(**kwargs)
 69 |         outpen = pathops.Path()
 70 |         pathops.difference(
 71 |             [_convert_vmobject_to_skia_path(subject)],
 72 |             [_convert_vmobject_to_skia_path(clip)],
 73 |             outpen.getPen(),
 74 |         )
 75 |         _convert_skia_path_to_vmobject(outpen, self)
 76 | 
 77 | 
 78 | class Intersection(VMobject):
 79 |     def __init__(self, *vmobjects: VMobject, **kwargs):
 80 |         if len(vmobjects) < 2:
 81 |             raise ValueError("At least 2 mobjects needed for Intersection.")
 82 |         super().__init__(**kwargs)
 83 |         outpen = pathops.Path()
 84 |         pathops.intersection(
 85 |             [_convert_vmobject_to_skia_path(vmobjects[0])],
 86 |             [_convert_vmobject_to_skia_path(vmobjects[1])],
 87 |             outpen.getPen(),
 88 |         )
 89 |         new_outpen = outpen
 90 |         for _i in range(2, len(vmobjects)):
 91 |             new_outpen = pathops.Path()
 92 |             pathops.intersection(
 93 |                 [outpen],
 94 |                 [_convert_vmobject_to_skia_path(vmobjects[_i])],
 95 |                 new_outpen.getPen(),
 96 |             )
 97 |             outpen = new_outpen
 98 |         _convert_skia_path_to_vmobject(outpen, self)
 99 | 
100 | 
101 | class Exclusion(VMobject):
102 |     def __init__(self, *vmobjects: VMobject, **kwargs):
103 |         if len(vmobjects) < 2:
104 |             raise ValueError("At least 2 mobjects needed for Exclusion.")
105 |         super().__init__(**kwargs)
106 |         outpen = pathops.Path()
107 |         pathops.xor(
108 |             [_convert_vmobject_to_skia_path(vmobjects[0])],
109 |             [_convert_vmobject_to_skia_path(vmobjects[1])],
110 |             outpen.getPen(),
111 |         )
112 |         new_outpen = outpen
113 |         for _i in range(2, len(vmobjects)):
114 |             new_outpen = pathops.Path()
115 |             pathops.xor(
116 |                 [outpen],
117 |                 [_convert_vmobject_to_skia_path(vmobjects[_i])],
118 |                 new_outpen.getPen(),
119 |             )
120 |             outpen = new_outpen
121 |         _convert_skia_path_to_vmobject(outpen, self)
122 | 


--------------------------------------------------------------------------------
/manimlib/mobject/changing.py:
--------------------------------------------------------------------------------
  1 | from __future__ import annotations
  2 | 
  3 | import numpy as np
  4 | 
  5 | from manimlib.constants import BLUE_B, BLUE_D, BLUE_E, GREY_BROWN, DEFAULT_MOBJECT_COLOR
  6 | from manimlib.mobject.mobject import Mobject
  7 | from manimlib.mobject.types.vectorized_mobject import VGroup
  8 | from manimlib.mobject.types.vectorized_mobject import VMobject
  9 | from manimlib.utils.rate_functions import smooth
 10 | 
 11 | from typing import TYPE_CHECKING
 12 | 
 13 | if TYPE_CHECKING:
 14 |     from typing import Callable, List, Iterable
 15 |     from manimlib.typing import ManimColor, Vect3, Self
 16 | 
 17 | 
 18 | class AnimatedBoundary(VGroup):
 19 |     def __init__(
 20 |         self,
 21 |         vmobject: VMobject,
 22 |         colors: List[ManimColor] = [BLUE_D, BLUE_B, BLUE_E, GREY_BROWN],
 23 |         max_stroke_width: float = 3.0,
 24 |         cycle_rate: float = 0.5,
 25 |         back_and_forth: bool = True,
 26 |         draw_rate_func: Callable[[float], float] = smooth,
 27 |         fade_rate_func: Callable[[float], float] = smooth,
 28 |         **kwargs
 29 |     ):
 30 |         super().__init__(**kwargs)
 31 |         self.vmobject: VMobject = vmobject
 32 |         self.colors = colors
 33 |         self.max_stroke_width = max_stroke_width
 34 |         self.cycle_rate = cycle_rate
 35 |         self.back_and_forth = back_and_forth
 36 |         self.draw_rate_func = draw_rate_func
 37 |         self.fade_rate_func = fade_rate_func
 38 | 
 39 |         self.boundary_copies: list[VMobject] = [
 40 |             vmobject.copy().set_style(
 41 |                 stroke_width=0,
 42 |                 fill_opacity=0
 43 |             )
 44 |             for x in range(2)
 45 |         ]
 46 |         self.add(*self.boundary_copies)
 47 |         self.total_time: float = 0
 48 |         self.add_updater(
 49 |             lambda m, dt: self.update_boundary_copies(dt)
 50 |         )
 51 | 
 52 |     def update_boundary_copies(self, dt: float) -> Self:
 53 |         # Not actual time, but something which passes at
 54 |         # an altered rate to make the implementation below
 55 |         # cleaner
 56 |         time = self.total_time * self.cycle_rate
 57 |         growing, fading = self.boundary_copies
 58 |         colors = self.colors
 59 |         msw = self.max_stroke_width
 60 |         vmobject = self.vmobject
 61 | 
 62 |         index = int(time % len(colors))
 63 |         alpha = time % 1
 64 |         draw_alpha = self.draw_rate_func(alpha)
 65 |         fade_alpha = self.fade_rate_func(alpha)
 66 | 
 67 |         if self.back_and_forth and int(time) % 2 == 1:
 68 |             bounds = (1 - draw_alpha, 1)
 69 |         else:
 70 |             bounds = (0, draw_alpha)
 71 |         self.full_family_become_partial(growing, vmobject, *bounds)
 72 |         growing.set_stroke(colors[index], width=msw)
 73 | 
 74 |         if time >= 1:
 75 |             self.full_family_become_partial(fading, vmobject, 0, 1)
 76 |             fading.set_stroke(
 77 |                 color=colors[index - 1],
 78 |                 width=(1 - fade_alpha) * msw
 79 |             )
 80 | 
 81 |         self.total_time += dt
 82 |         return self
 83 | 
 84 |     def full_family_become_partial(
 85 |         self,
 86 |         mob1: VMobject,
 87 |         mob2: VMobject,
 88 |         a: float,
 89 |         b: float
 90 |     ) -> Self:
 91 |         family1 = mob1.family_members_with_points()
 92 |         family2 = mob2.family_members_with_points()
 93 |         for sm1, sm2 in zip(family1, family2):
 94 |             sm1.pointwise_become_partial(sm2, a, b)
 95 |         return self
 96 | 
 97 | 
 98 | class TracedPath(VMobject):
 99 |     def __init__(
100 |         self,
101 |         traced_point_func: Callable[[], Vect3],
102 |         time_traced: float = np.inf,
103 |         time_per_anchor: float = 1.0 / 15,
104 |         stroke_width: float | Iterable[float] = 2.0,
105 |         stroke_color: ManimColor = DEFAULT_MOBJECT_COLOR,
106 |         **kwargs
107 |     ):
108 |         super().__init__(**kwargs)
109 |         self.traced_point_func = traced_point_func
110 |         self.time_traced = time_traced
111 |         self.time_per_anchor = time_per_anchor
112 |         self.time: float = 0
113 |         self.traced_points: list[np.ndarray] = []
114 |         self.add_updater(lambda m, dt: m.update_path(dt))
115 |         self.always.set_stroke(stroke_color, stroke_width)
116 | 
117 |     def update_path(self, dt: float) -> Self:
118 |         if dt == 0:
119 |             return self
120 |         point = self.traced_point_func().copy()
121 |         self.traced_points.append(point)
122 | 
123 |         if self.time_traced < np.inf:
124 |             n_relevant_points = int(self.time_traced / dt + 0.5)
125 |             n_tps = len(self.traced_points)
126 |             if n_tps < n_relevant_points:
127 |                 points = self.traced_points + [point] * (n_relevant_points - n_tps)
128 |             else:
129 |                 points = self.traced_points[n_tps - n_relevant_points:]
130 |             # Every now and then refresh the list
131 |             if n_tps > 10 * n_relevant_points:
132 |                 self.traced_points = self.traced_points[-n_relevant_points:]
133 |         else:
134 |             points = self.traced_points
135 | 
136 |         if points:
137 |             self.set_points_smoothly(points)
138 | 
139 |         self.time += dt
140 |         return self
141 | 
142 | 
143 | class TracingTail(TracedPath):
144 |     def __init__(
145 |         self,
146 |         mobject_or_func: Mobject | Callable[[], np.ndarray],
147 |         time_traced: float = 1.0,
148 |         stroke_width: float | Iterable[float] = (0, 3),
149 |         stroke_opacity: float | Iterable[float] = (0, 1),
150 |         stroke_color: ManimColor = DEFAULT_MOBJECT_COLOR,
151 |         **kwargs
152 |     ):
153 |         if isinstance(mobject_or_func, Mobject):
154 |             func = mobject_or_func.get_center
155 |         else:
156 |             func = mobject_or_func
157 |         super().__init__(
158 |             func,
159 |             time_traced=time_traced,
160 |             stroke_width=stroke_width,
161 |             stroke_opacity=stroke_opacity,
162 |             stroke_color=stroke_color,
163 |             **kwargs
164 |         )
165 |         self.add_updater(lambda m: m.set_stroke(width=stroke_width, opacity=stroke_opacity))
166 | 


--------------------------------------------------------------------------------
/manimlib/mobject/frame.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from manimlib.constants import BLACK, GREY_E
 4 | from manimlib.constants import FRAME_HEIGHT
 5 | from manimlib.mobject.geometry import Rectangle
 6 | 
 7 | from typing import TYPE_CHECKING
 8 | if TYPE_CHECKING:
 9 |     from manimlib.typing import ManimColor
10 | 
11 | 
12 | class ScreenRectangle(Rectangle):
13 |     def __init__(
14 |         self,
15 |         aspect_ratio: float = 16.0 / 9.0,
16 |         height: float = 4,
17 |         **kwargs
18 |     ):
19 |         super().__init__(
20 |             width=aspect_ratio * height,
21 |             height=height,
22 |             **kwargs
23 |         )
24 | 
25 | 
26 | class FullScreenRectangle(ScreenRectangle):
27 |     def __init__(
28 |         self,
29 |         height: float = FRAME_HEIGHT,
30 |         fill_color: ManimColor = GREY_E,
31 |         fill_opacity: float = 1,
32 |         stroke_width: float = 0,
33 |         **kwargs,
34 |     ):
35 |         super().__init__(
36 |             height=height,
37 |             fill_color=fill_color,
38 |             fill_opacity=fill_opacity,
39 |             stroke_width=stroke_width,
40 |             **kwargs
41 |         )
42 | 
43 | 
44 | class FullScreenFadeRectangle(FullScreenRectangle):
45 |     def __init__(
46 |         self,
47 |         stroke_width: float = 0.0,
48 |         fill_color: ManimColor = BLACK,
49 |         fill_opacity: float = 0.7,
50 |         **kwargs,
51 |     ):
52 |         super().__init__(
53 |             stroke_width=stroke_width,
54 |             fill_color=fill_color,
55 |             fill_opacity=fill_opacity,
56 |         )
57 | 


--------------------------------------------------------------------------------
/manimlib/mobject/functions.py:
--------------------------------------------------------------------------------
  1 | from __future__ import annotations
  2 | 
  3 | from isosurfaces import plot_isoline
  4 | import numpy as np
  5 | 
  6 | from manimlib.constants import FRAME_X_RADIUS, FRAME_Y_RADIUS
  7 | from manimlib.constants import YELLOW
  8 | from manimlib.mobject.types.vectorized_mobject import VMobject
  9 | 
 10 | from typing import TYPE_CHECKING
 11 | 
 12 | if TYPE_CHECKING:
 13 |     from typing import Callable, Sequence, Tuple
 14 |     from manimlib.typing import ManimColor, Vect3
 15 | 
 16 | 
 17 | class ParametricCurve(VMobject):
 18 |     def __init__(
 19 |         self,
 20 |         t_func: Callable[[float], Sequence[float] | Vect3],
 21 |         t_range: Tuple[float, float, float] = (0, 1, 0.1),
 22 |         epsilon: float = 1e-8,
 23 |         # TODO, automatically figure out discontinuities
 24 |         discontinuities: Sequence[float] = [],
 25 |         use_smoothing: bool = True,
 26 |         **kwargs
 27 |     ):
 28 |         self.t_func = t_func
 29 |         self.t_range = t_range
 30 |         self.epsilon = epsilon
 31 |         self.discontinuities = discontinuities
 32 |         self.use_smoothing = use_smoothing
 33 |         super().__init__(**kwargs)
 34 | 
 35 |     def get_point_from_function(self, t: float) -> Vect3:
 36 |         return np.array(self.t_func(t))
 37 | 
 38 |     def init_points(self):
 39 |         t_min, t_max, step = self.t_range
 40 | 
 41 |         jumps = np.array(self.discontinuities)
 42 |         jumps = jumps[(jumps > t_min) & (jumps < t_max)]
 43 |         boundary_times = [t_min, t_max, *(jumps - self.epsilon), *(jumps + self.epsilon)]
 44 |         boundary_times.sort()
 45 |         for t1, t2 in zip(boundary_times[0::2], boundary_times[1::2]):
 46 |             t_range = [*np.arange(t1, t2, step), t2]
 47 |             points = np.array([self.t_func(t) for t in t_range])
 48 |             self.start_new_path(points[0])
 49 |             self.add_points_as_corners(points[1:])
 50 |         if self.use_smoothing:
 51 |             self.make_smooth(approx=True)
 52 |         if not self.has_points():
 53 |             self.set_points(np.array([self.t_func(t_min)]))
 54 |         return self
 55 | 
 56 |     def get_t_func(self):
 57 |         return self.t_func
 58 | 
 59 |     def get_function(self):
 60 |         if hasattr(self, "underlying_function"):
 61 |             return self.underlying_function
 62 |         if hasattr(self, "function"):
 63 |             return self.function
 64 | 
 65 |     def get_x_range(self):
 66 |         if hasattr(self, "x_range"):
 67 |             return self.x_range
 68 | 
 69 | 
 70 | class FunctionGraph(ParametricCurve):
 71 |     def __init__(
 72 |         self,
 73 |         function: Callable[[float], float],
 74 |         x_range: Tuple[float, float, float] = (-8, 8, 0.25),
 75 |         color: ManimColor = YELLOW,
 76 |         **kwargs
 77 |     ):
 78 |         self.function = function
 79 |         self.x_range = x_range
 80 | 
 81 |         def parametric_function(t):
 82 |             return [t, function(t), 0]
 83 | 
 84 |         super().__init__(parametric_function, self.x_range, **kwargs)
 85 | 
 86 | 
 87 | class ImplicitFunction(VMobject):
 88 |     def __init__(
 89 |         self,
 90 |         func: Callable[[float, float], float],
 91 |         x_range: Tuple[float, float] = (-FRAME_X_RADIUS, FRAME_X_RADIUS),
 92 |         y_range: Tuple[float, float] = (-FRAME_Y_RADIUS, FRAME_Y_RADIUS),
 93 |         min_depth: int = 5,
 94 |         max_quads: int = 1500,
 95 |         use_smoothing: bool = False,
 96 |         joint_type: str = 'no_joint',
 97 |         **kwargs
 98 |     ):
 99 |         super().__init__(joint_type=joint_type, **kwargs)
100 | 
101 |         p_min, p_max = (
102 |             np.array([x_range[0], y_range[0]]),
103 |             np.array([x_range[1], y_range[1]]),
104 |         )
105 |         curves = plot_isoline(
106 |             fn=lambda u: func(u[0], u[1]),
107 |             pmin=p_min,
108 |             pmax=p_max,
109 |             min_depth=min_depth,
110 |             max_quads=max_quads,
111 |         )  # returns a list of lists of 2D points
112 |         curves = [
113 |             np.pad(curve, [(0, 0), (0, 1)])
114 |             for curve in curves
115 |             if curve != []
116 |         ]  # add z coord as 0
117 |         for curve in curves:
118 |             self.start_new_path(curve[0])
119 |             self.add_points_as_corners(curve[1:])
120 |         if use_smoothing:
121 |             self.make_smooth()
122 | 


--------------------------------------------------------------------------------
/manimlib/mobject/mobject_update_utils.py:
--------------------------------------------------------------------------------
  1 | from __future__ import annotations
  2 | 
  3 | import inspect
  4 | 
  5 | from manimlib.constants import DEG
  6 | from manimlib.constants import RIGHT
  7 | from manimlib.mobject.mobject import Mobject
  8 | from manimlib.utils.simple_functions import clip
  9 | 
 10 | from typing import TYPE_CHECKING
 11 | 
 12 | if TYPE_CHECKING:
 13 |     from typing import Callable
 14 | 
 15 |     import numpy as np
 16 | 
 17 |     from manimlib.animation.animation import Animation
 18 | 
 19 | 
 20 | def assert_is_mobject_method(method):
 21 |     assert inspect.ismethod(method)
 22 |     mobject = method.__self__
 23 |     assert isinstance(mobject, Mobject)
 24 | 
 25 | 
 26 | def always(method, *args, **kwargs):
 27 |     assert_is_mobject_method(method)
 28 |     mobject = method.__self__
 29 |     func = method.__func__
 30 |     mobject.add_updater(lambda m: func(m, *args, **kwargs))
 31 |     return mobject
 32 | 
 33 | 
 34 | def f_always(method, *arg_generators, **kwargs):
 35 |     """
 36 |     More functional version of always, where instead
 37 |     of taking in args, it takes in functions which output
 38 |     the relevant arguments.
 39 |     """
 40 |     assert_is_mobject_method(method)
 41 |     mobject = method.__self__
 42 |     func = method.__func__
 43 | 
 44 |     def updater(mob):
 45 |         args = [
 46 |             arg_generator()
 47 |             for arg_generator in arg_generators
 48 |         ]
 49 |         func(mob, *args, **kwargs)
 50 | 
 51 |     mobject.add_updater(updater)
 52 |     return mobject
 53 | 
 54 | 
 55 | def always_redraw(func: Callable[..., Mobject], *args, **kwargs) -> Mobject:
 56 |     mob = func(*args, **kwargs)
 57 |     mob.add_updater(lambda m: mob.become(func(*args, **kwargs)))
 58 |     return mob
 59 | 
 60 | 
 61 | def always_shift(
 62 |     mobject: Mobject,
 63 |     direction: np.ndarray = RIGHT,
 64 |     rate: float = 0.1
 65 | ) -> Mobject:
 66 |     mobject.add_updater(
 67 |         lambda m, dt: m.shift(dt * rate * direction)
 68 |     )
 69 |     return mobject
 70 | 
 71 | 
 72 | def always_rotate(
 73 |     mobject: Mobject,
 74 |     rate: float = 20 * DEG,
 75 |     **kwargs
 76 | ) -> Mobject:
 77 |     mobject.add_updater(
 78 |         lambda m, dt: m.rotate(dt * rate, **kwargs)
 79 |     )
 80 |     return mobject
 81 | 
 82 | 
 83 | def turn_animation_into_updater(
 84 |     animation: Animation,
 85 |     cycle: bool = False,
 86 |     **kwargs
 87 | ) -> Mobject:
 88 |     """
 89 |     Add an updater to the animation's mobject which applies
 90 |     the interpolation and update functions of the animation
 91 | 
 92 |     If cycle is True, this repeats over and over.  Otherwise,
 93 |     the updater will be popped uplon completion
 94 |     """
 95 |     mobject = animation.mobject
 96 |     animation.update_rate_info(**kwargs)
 97 |     animation.suspend_mobject_updating = False
 98 |     animation.begin()
 99 |     animation.total_time = 0
100 | 
101 |     def update(m, dt):
102 |         run_time = animation.get_run_time()
103 |         time_ratio = animation.total_time / run_time
104 |         if cycle:
105 |             alpha = time_ratio % 1
106 |         else:
107 |             alpha = clip(time_ratio, 0, 1)
108 |             if alpha >= 1:
109 |                 animation.finish()
110 |                 m.remove_updater(update)
111 |                 return
112 |         animation.interpolate(alpha)
113 |         animation.update_mobjects(dt)
114 |         animation.total_time += dt
115 | 
116 |     mobject.add_updater(update)
117 |     return mobject
118 | 
119 | 
120 | def cycle_animation(animation: Animation, **kwargs) -> Mobject:
121 |     return turn_animation_into_updater(
122 |         animation, cycle=True, **kwargs
123 |     )
124 | 


--------------------------------------------------------------------------------
/manimlib/mobject/shape_matchers.py:
--------------------------------------------------------------------------------
  1 | from __future__ import annotations
  2 | 
  3 | from colour import Color
  4 | 
  5 | from manimlib.config import manim_config
  6 | from manimlib.constants import BLACK, RED, YELLOW, DEFAULT_MOBJECT_COLOR
  7 | from manimlib.constants import DL, DOWN, DR, LEFT, RIGHT, UL, UR
  8 | from manimlib.constants import SMALL_BUFF
  9 | from manimlib.mobject.geometry import Line
 10 | from manimlib.mobject.geometry import Rectangle
 11 | from manimlib.mobject.types.vectorized_mobject import VGroup
 12 | from manimlib.mobject.types.vectorized_mobject import VMobject
 13 | 
 14 | from typing import TYPE_CHECKING
 15 | 
 16 | if TYPE_CHECKING:
 17 |     from typing import Sequence
 18 |     from manimlib.mobject.mobject import Mobject
 19 |     from manimlib.typing import ManimColor, Self
 20 | 
 21 | 
 22 | class SurroundingRectangle(Rectangle):
 23 |     def __init__(
 24 |         self,
 25 |         mobject: Mobject,
 26 |         buff: float = SMALL_BUFF,
 27 |         color: ManimColor = YELLOW,
 28 |         **kwargs
 29 |     ):
 30 |         super().__init__(color=color, **kwargs)
 31 |         self.buff = buff
 32 |         self.surround(mobject)
 33 |         if mobject.is_fixed_in_frame():
 34 |             self.fix_in_frame()
 35 | 
 36 |     def surround(self, mobject, buff=None) -> Self:
 37 |         self.mobject = mobject
 38 |         self.buff = buff if buff is not None else self.buff
 39 |         super().surround(mobject, self.buff)
 40 |         return self
 41 | 
 42 |     def set_buff(self, buff) -> Self:
 43 |         self.buff = buff
 44 |         self.surround(self.mobject)
 45 |         return self
 46 | 
 47 | 
 48 | class BackgroundRectangle(SurroundingRectangle):
 49 |     def __init__(
 50 |         self,
 51 |         mobject: Mobject,
 52 |         color: ManimColor = None,
 53 |         stroke_width: float = 0,
 54 |         stroke_opacity: float = 0,
 55 |         fill_opacity: float = 0.75,
 56 |         buff: float = 0,
 57 |         **kwargs
 58 |     ):
 59 |         if color is None:
 60 |             color = manim_config.camera.background_color
 61 |         super().__init__(
 62 |             mobject,
 63 |             color=color,
 64 |             stroke_width=stroke_width,
 65 |             stroke_opacity=stroke_opacity,
 66 |             fill_opacity=fill_opacity,
 67 |             buff=buff,
 68 |             **kwargs
 69 |         )
 70 |         self.original_fill_opacity = fill_opacity
 71 | 
 72 |     def pointwise_become_partial(self, mobject: Mobject, a: float, b: float) -> Self:
 73 |         self.set_fill(opacity=b * self.original_fill_opacity)
 74 |         return self
 75 | 
 76 |     def set_style(
 77 |         self,
 78 |         stroke_color: ManimColor | None = None,
 79 |         stroke_width: float | None = None,
 80 |         fill_color: ManimColor | None = None,
 81 |         fill_opacity: float | None = None,
 82 |         family: bool = True,
 83 |         **kwargs
 84 |     ) -> Self:
 85 |         # Unchangeable style, except for fill_opacity
 86 |         VMobject.set_style(
 87 |             self,
 88 |             stroke_color=BLACK,
 89 |             stroke_width=0,
 90 |             fill_color=BLACK,
 91 |             fill_opacity=fill_opacity
 92 |         )
 93 |         return self
 94 | 
 95 |     def get_fill_color(self) -> Color:
 96 |         return Color(self.color)
 97 | 
 98 | 
 99 | class Cross(VGroup):
100 |     def __init__(
101 |         self,
102 |         mobject: Mobject,
103 |         stroke_color: ManimColor = RED,
104 |         stroke_width: float | Sequence[float] = [0, 6, 0],
105 |         **kwargs
106 |     ):
107 |         super().__init__(
108 |             Line(UL, DR),
109 |             Line(UR, DL),
110 |         )
111 |         self.insert_n_curves(20)
112 |         self.replace(mobject, stretch=True)
113 |         self.set_stroke(stroke_color, width=stroke_width)
114 | 
115 | 
116 | class Underline(Line):
117 |     def __init__(
118 |         self,
119 |         mobject: Mobject,
120 |         buff: float = SMALL_BUFF,
121 |         stroke_color=DEFAULT_MOBJECT_COLOR,
122 |         stroke_width: float | Sequence[float] = [0, 3, 3, 0],
123 |         stretch_factor=1.2,
124 |         **kwargs
125 |     ):
126 |         super().__init__(LEFT, RIGHT, **kwargs)
127 |         if not isinstance(stroke_width, (float, int)):
128 |             self.insert_n_curves(len(stroke_width) - 2)
129 |         self.set_stroke(stroke_color, stroke_width)
130 |         self.set_width(mobject.get_width() * stretch_factor)
131 |         self.next_to(mobject, DOWN, buff=buff)
132 | 


--------------------------------------------------------------------------------
/manimlib/mobject/svg/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3b1b/manim/41613db7eca9eabbb40aa830631e5206c9282f0f/manimlib/mobject/svg/__init__.py


--------------------------------------------------------------------------------
/manimlib/mobject/svg/brace.py:
--------------------------------------------------------------------------------
  1 | from __future__ import annotations
  2 | 
  3 | import math
  4 | import copy
  5 | 
  6 | import numpy as np
  7 | 
  8 | from manimlib.constants import DEFAULT_MOBJECT_TO_MOBJECT_BUFF, SMALL_BUFF
  9 | from manimlib.constants import DOWN, LEFT, ORIGIN, RIGHT, DL, DR, UL, UP
 10 | from manimlib.constants import PI
 11 | from manimlib.animation.composition import AnimationGroup
 12 | from manimlib.animation.fading import FadeIn
 13 | from manimlib.animation.growing import GrowFromCenter
 14 | from manimlib.mobject.svg.tex_mobject import Tex
 15 | from manimlib.mobject.svg.tex_mobject import TexText
 16 | from manimlib.mobject.svg.text_mobject import Text
 17 | from manimlib.mobject.types.vectorized_mobject import VGroup
 18 | from manimlib.mobject.types.vectorized_mobject import VMobject
 19 | from manimlib.utils.iterables import listify
 20 | from manimlib.utils.space_ops import get_norm
 21 | 
 22 | from typing import TYPE_CHECKING
 23 | 
 24 | if TYPE_CHECKING:
 25 |     from typing import Iterable
 26 | 
 27 |     from manimlib.animation.animation import Animation
 28 |     from manimlib.mobject.mobject import Mobject
 29 |     from manimlib.typing import Vect3
 30 | 
 31 | 
 32 | class Brace(Tex):
 33 |     def __init__(
 34 |         self,
 35 |         mobject: Mobject,
 36 |         direction: Vect3 = DOWN,
 37 |         buff: float = 0.2,
 38 |         tex_string: str = R"\underbrace{\qquad}",
 39 |         **kwargs
 40 |     ):
 41 |         super().__init__(tex_string, **kwargs)
 42 | 
 43 |         angle = -math.atan2(*direction[:2]) + PI
 44 |         mobject.rotate(-angle, about_point=ORIGIN)
 45 |         left = mobject.get_corner(DL)
 46 |         right = mobject.get_corner(DR)
 47 |         target_width = right[0] - left[0]
 48 | 
 49 |         self.tip_point_index = np.argmin(self.get_all_points()[:, 1])
 50 |         self.set_initial_width(target_width)
 51 |         self.shift(left - self.get_corner(UL) + buff * DOWN)
 52 |         for mob in mobject, self:
 53 |             mob.rotate(angle, about_point=ORIGIN)
 54 | 
 55 |     def set_initial_width(self, width: float):
 56 |         width_diff = width - self.get_width()
 57 |         if width_diff > 0:
 58 |             for tip, rect, vect in [(self[0], self[1], RIGHT), (self[5], self[4], LEFT)]:
 59 |                 rect.set_width(
 60 |                     width_diff / 2 + rect.get_width(),
 61 |                     about_edge=vect, stretch=True
 62 |                 )
 63 |                 tip.shift(-width_diff / 2 * vect)
 64 |         else:
 65 |             self.set_width(width, stretch=True)
 66 |         return self
 67 | 
 68 |     def put_at_tip(
 69 |         self,
 70 |         mob: Mobject,
 71 |         use_next_to: bool = True,
 72 |         **kwargs
 73 |     ):
 74 |         if use_next_to:
 75 |             mob.next_to(
 76 |                 self.get_tip(),
 77 |                 np.round(self.get_direction()),
 78 |                 **kwargs
 79 |             )
 80 |         else:
 81 |             mob.move_to(self.get_tip())
 82 |             buff = kwargs.get("buff", DEFAULT_MOBJECT_TO_MOBJECT_BUFF)
 83 |             shift_distance = mob.get_width() / 2.0 + buff
 84 |             mob.shift(self.get_direction() * shift_distance)
 85 |         return self
 86 | 
 87 |     def get_text(self, text: str, **kwargs) -> Text:
 88 |         buff = kwargs.pop("buff", SMALL_BUFF)
 89 |         text_mob = Text(text, **kwargs)
 90 |         self.put_at_tip(text_mob, buff=buff)
 91 |         return text_mob
 92 | 
 93 |     def get_tex(self, *tex: str, **kwargs) -> Tex:
 94 |         buff = kwargs.pop("buff", SMALL_BUFF)
 95 |         tex_mob = Tex(*tex, **kwargs)
 96 |         self.put_at_tip(tex_mob, buff=buff)
 97 |         return tex_mob
 98 | 
 99 |     def get_tip(self) -> np.ndarray:
100 |         # Very specific to the LaTeX representation
101 |         # of a brace, but it's the only way I can think
102 |         # of to get the tip regardless of orientation.
103 |         return self.get_all_points()[self.tip_point_index]
104 | 
105 |     def get_direction(self) -> np.ndarray:
106 |         vect = self.get_tip() - self.get_center()
107 |         return vect / get_norm(vect)
108 | 
109 | 
110 | class BraceLabel(VMobject):
111 |     label_constructor: type = Tex
112 | 
113 |     def __init__(
114 |         self,
115 |         obj: VMobject | list[VMobject],
116 |         text: str | Iterable[str],
117 |         brace_direction: np.ndarray = DOWN,
118 |         label_scale: float = 1.0,
119 |         label_buff: float = DEFAULT_MOBJECT_TO_MOBJECT_BUFF,
120 |         **kwargs
121 |     ) -> None:
122 |         super().__init__(**kwargs)
123 |         self.brace_direction = brace_direction
124 |         self.label_scale = label_scale
125 |         self.label_buff = label_buff
126 | 
127 |         if isinstance(obj, list):
128 |             obj = VGroup(*obj)
129 |         self.brace = Brace(obj, brace_direction, **kwargs)
130 | 
131 |         self.label = self.label_constructor(*listify(text), **kwargs)
132 |         self.label.scale(self.label_scale)
133 | 
134 |         self.brace.put_at_tip(self.label, buff=self.label_buff)
135 |         self.set_submobjects([self.brace, self.label])
136 | 
137 |     def creation_anim(
138 |         self,
139 |         label_anim: Animation = FadeIn,
140 |         brace_anim: Animation = GrowFromCenter
141 |     ) -> AnimationGroup:
142 |         return AnimationGroup(brace_anim(self.brace), label_anim(self.label))
143 | 
144 |     def shift_brace(self, obj: VMobject | list[VMobject], **kwargs):
145 |         if isinstance(obj, list):
146 |             obj = VMobject(*obj)
147 |         self.brace = Brace(obj, self.brace_direction, **kwargs)
148 |         self.brace.put_at_tip(self.label)
149 |         self.submobjects[0] = self.brace
150 |         return self
151 | 
152 |     def change_label(self, *text: str, **kwargs):
153 |         self.label = self.label_constructor(*text, **kwargs)
154 |         if self.label_scale != 1:
155 |             self.label.scale(self.label_scale)
156 | 
157 |         self.brace.put_at_tip(self.label)
158 |         self.submobjects[1] = self.label
159 |         return self
160 | 
161 |     def change_brace_label(self, obj: VMobject | list[VMobject], *text: str):
162 |         self.shift_brace(obj)
163 |         self.change_label(*text)
164 |         return self
165 | 
166 |     def copy(self):
167 |         copy_mobject = copy.copy(self)
168 |         copy_mobject.brace = self.brace.copy()
169 |         copy_mobject.label = self.label.copy()
170 |         copy_mobject.set_submobjects([copy_mobject.brace, copy_mobject.label])
171 | 
172 |         return copy_mobject
173 | 
174 | 
175 | class BraceText(BraceLabel):
176 |     label_constructor: type = TexText
177 | 
178 | 
179 | class LineBrace(Brace):
180 |     def __init__(self, line: Line, direction=UP, **kwargs):
181 |         angle = line.get_angle()
182 |         line.rotate(-angle)
183 |         super().__init__(line, direction, **kwargs)
184 |         line.rotate(angle)
185 |         self.rotate(angle, about_point=line.get_center())
186 | 


--------------------------------------------------------------------------------
/manimlib/mobject/svg/special_tex.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from manimlib.constants import MED_SMALL_BUFF, DEFAULT_MOBJECT_COLOR, GREY_C
 4 | from manimlib.constants import DOWN, LEFT, RIGHT, UP
 5 | from manimlib.constants import FRAME_WIDTH
 6 | from manimlib.constants import MED_LARGE_BUFF, SMALL_BUFF
 7 | from manimlib.mobject.geometry import Line
 8 | from manimlib.mobject.types.vectorized_mobject import VGroup
 9 | from manimlib.mobject.svg.tex_mobject import TexText
10 | 
11 | 
12 | from typing import TYPE_CHECKING
13 | 
14 | if TYPE_CHECKING:
15 |     from manimlib.typing import ManimColor, Vect3
16 | 
17 | 
18 | class BulletedList(VGroup):
19 |     def __init__(
20 |         self,
21 |         *items: str,
22 |         buff: float = MED_LARGE_BUFF,
23 |         aligned_edge: Vect3 = LEFT,
24 |         **kwargs
25 |     ):
26 |         labelled_content = [R"\item " + item for item in items]
27 |         tex_string = "\n".join([
28 |             R"\begin{itemize}",
29 |             *labelled_content,
30 |             R"\end{itemize}"
31 |         ])
32 |         tex_text = TexText(tex_string, isolate=labelled_content, **kwargs)
33 |         lines = (tex_text.select_part(part) for part in labelled_content)
34 | 
35 |         super().__init__(*lines)
36 | 
37 |         self.arrange(DOWN, buff=buff, aligned_edge=aligned_edge)
38 | 
39 |     def fade_all_but(self, index: int, opacity: float = 0.25, scale_factor=0.7) -> None:
40 |         max_dot_height = max([item[0].get_height() for item in self.submobjects])
41 |         for i, part in enumerate(self.submobjects):
42 |             trg_dot_height = (1.0 if i == index else scale_factor) * max_dot_height
43 |             part.set_fill(opacity=(1.0 if i == index else opacity))
44 |             part.scale(trg_dot_height / part[0].get_height(), about_edge=LEFT)
45 | 
46 | 
47 | class TexTextFromPresetString(TexText):
48 |     tex: str = ""
49 |     default_color: ManimColor = DEFAULT_MOBJECT_COLOR
50 | 
51 |     def __init__(self, **kwargs):
52 |         super().__init__(
53 |             self.tex,
54 |             color=kwargs.pop("color", self.default_color),
55 |             **kwargs
56 |         )
57 | 
58 | 
59 | class Title(TexText):
60 |     def __init__(
61 |         self,
62 |         *text_parts: str,
63 |         font_size: int = 72,
64 |         include_underline: bool = True,
65 |         underline_width: float = FRAME_WIDTH - 2,
66 |         # This will override underline_width
67 |         match_underline_width_to_text: bool = False,
68 |         underline_buff: float = SMALL_BUFF,
69 |         underline_style: dict = dict(stroke_width=2, stroke_color=GREY_C),
70 |         **kwargs
71 |     ):
72 |         super().__init__(*text_parts, font_size=font_size, **kwargs)
73 |         self.to_edge(UP, buff=MED_SMALL_BUFF)
74 |         if include_underline:
75 |             underline = Line(LEFT, RIGHT, **underline_style)
76 |             underline.next_to(self, DOWN, buff=underline_buff)
77 |             if match_underline_width_to_text:
78 |                 underline.match_width(self)
79 |             else:
80 |                 underline.set_width(underline_width)
81 |             self.add(underline)
82 |             self.underline = underline
83 | 


--------------------------------------------------------------------------------
/manimlib/mobject/types/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3b1b/manim/41613db7eca9eabbb40aa830631e5206c9282f0f/manimlib/mobject/types/__init__.py


--------------------------------------------------------------------------------
/manimlib/mobject/types/dot_cloud.py:
--------------------------------------------------------------------------------
  1 | from __future__ import annotations
  2 | 
  3 | import moderngl
  4 | import numpy as np
  5 | 
  6 | from manimlib.constants import GREY_C, YELLOW
  7 | from manimlib.constants import ORIGIN, NULL_POINTS
  8 | from manimlib.mobject.mobject import Mobject
  9 | from manimlib.mobject.types.point_cloud_mobject import PMobject
 10 | from manimlib.utils.iterables import resize_with_interpolation
 11 | 
 12 | from typing import TYPE_CHECKING
 13 | 
 14 | if TYPE_CHECKING:
 15 |     import numpy.typing as npt
 16 |     from typing import Sequence, Tuple
 17 |     from manimlib.typing import ManimColor, Vect3, Vect3Array, Self
 18 | 
 19 | 
 20 | DEFAULT_DOT_RADIUS = 0.05
 21 | DEFAULT_GLOW_DOT_RADIUS = 0.2
 22 | DEFAULT_GRID_HEIGHT = 6
 23 | DEFAULT_BUFF_RATIO = 0.5
 24 | 
 25 | 
 26 | class DotCloud(PMobject):
 27 |     shader_folder: str = "true_dot"
 28 |     render_primitive: int = moderngl.POINTS
 29 |     data_dtype: Sequence[Tuple[str, type, Tuple[int]]] = [
 30 |         ('point', np.float32, (3,)),
 31 |         ('radius', np.float32, (1,)),
 32 |         ('rgba', np.float32, (4,)),
 33 |     ]
 34 | 
 35 |     def __init__(
 36 |         self,
 37 |         points: Vect3Array = NULL_POINTS,
 38 |         color: ManimColor = GREY_C,
 39 |         opacity: float = 1.0,
 40 |         radius: float = DEFAULT_DOT_RADIUS,
 41 |         glow_factor: float = 0.0,
 42 |         anti_alias_width: float = 2.0,
 43 |         **kwargs
 44 |     ):
 45 |         self.radius = radius
 46 |         self.glow_factor = glow_factor
 47 |         self.anti_alias_width = anti_alias_width
 48 | 
 49 |         super().__init__(
 50 |             color=color,
 51 |             opacity=opacity,
 52 |             **kwargs
 53 |         )
 54 |         self.set_radius(self.radius)
 55 | 
 56 |         if points is not None:
 57 |             self.set_points(points)
 58 | 
 59 |     def init_uniforms(self) -> None:
 60 |         super().init_uniforms()
 61 |         self.uniforms["glow_factor"] = self.glow_factor
 62 |         self.uniforms["anti_alias_width"] = self.anti_alias_width
 63 | 
 64 |     def to_grid(
 65 |         self,
 66 |         n_rows: int,
 67 |         n_cols: int,
 68 |         n_layers: int = 1,
 69 |         buff_ratio: float | None = None,
 70 |         h_buff_ratio: float = 1.0,
 71 |         v_buff_ratio: float = 1.0,
 72 |         d_buff_ratio: float = 1.0,
 73 |         height: float = DEFAULT_GRID_HEIGHT,
 74 |     ) -> Self:
 75 |         n_points = n_rows * n_cols * n_layers
 76 |         points = np.repeat(range(n_points), 3, axis=0).reshape((n_points, 3))
 77 |         points[:, 0] = points[:, 0] % n_cols
 78 |         points[:, 1] = (points[:, 1] // n_cols) % n_rows
 79 |         points[:, 2] = points[:, 2] // (n_rows * n_cols)
 80 |         self.set_points(points.astype(float))
 81 | 
 82 |         if buff_ratio is not None:
 83 |             v_buff_ratio = buff_ratio
 84 |             h_buff_ratio = buff_ratio
 85 |             d_buff_ratio = buff_ratio
 86 | 
 87 |         radius = self.get_radius()
 88 |         ns = [n_cols, n_rows, n_layers]
 89 |         brs = [h_buff_ratio, v_buff_ratio, d_buff_ratio]
 90 |         self.set_radius(0)
 91 |         for n, br, dim in zip(ns, brs, range(3)):
 92 |             self.rescale_to_fit(2 * radius * (1 + br) * (n - 1), dim, stretch=True)
 93 |         self.set_radius(radius)
 94 |         if height is not None:
 95 |             self.set_height(height)
 96 |         self.center()
 97 |         return self
 98 | 
 99 |     @Mobject.affects_data
100 |     def set_radii(self, radii: npt.ArrayLike) -> Self:
101 |         n_points = self.get_num_points()
102 |         radii = np.array(radii).reshape((len(radii), 1))
103 |         self.data["radius"][:] = resize_with_interpolation(radii, n_points)
104 |         self.refresh_bounding_box()
105 |         return self
106 | 
107 |     def get_radii(self) -> np.ndarray:
108 |         return self.data["radius"]
109 | 
110 |     @Mobject.affects_data
111 |     def set_radius(self, radius: float) -> Self:
112 |         data = self.data if self.get_num_points() > 0 else self._data_defaults
113 |         data["radius"][:] = radius
114 |         self.refresh_bounding_box()
115 |         return self
116 | 
117 |     def get_radius(self) -> float:
118 |         return self.get_radii().max()
119 | 
120 |     def scale_radii(self, scale_factor: float) -> Self:
121 |         self.set_radius(scale_factor * self.get_radii())
122 |         return self
123 | 
124 |     def set_glow_factor(self, glow_factor: float) -> Self:
125 |         self.uniforms["glow_factor"] = glow_factor
126 |         return self
127 | 
128 |     def get_glow_factor(self) -> float:
129 |         return self.uniforms["glow_factor"]
130 | 
131 |     def compute_bounding_box(self) -> Vect3Array:
132 |         bb = super().compute_bounding_box()
133 |         radius = self.get_radius()
134 |         bb[0] += np.full((3,), -radius)
135 |         bb[2] += np.full((3,), radius)
136 |         return bb
137 | 
138 |     def scale(
139 |         self,
140 |         scale_factor: float | npt.ArrayLike,
141 |         scale_radii: bool = True,
142 |         **kwargs
143 |     ) -> Self:
144 |         super().scale(scale_factor, **kwargs)
145 |         if scale_radii:
146 |             self.set_radii(scale_factor * self.get_radii())
147 |         return self
148 | 
149 |     def make_3d(
150 |         self,
151 |         reflectiveness: float = 0.5,
152 |         gloss: float = 0.1,
153 |         shadow: float = 0.2
154 |     ) -> Self:
155 |         self.set_shading(reflectiveness, gloss, shadow)
156 |         self.apply_depth_test()
157 |         return self
158 | 
159 | 
160 | class TrueDot(DotCloud):
161 |     def __init__(self, center: Vect3 = ORIGIN, **kwargs):
162 |         super().__init__(points=np.array([center]), **kwargs)
163 | 
164 | 
165 | class GlowDots(DotCloud):
166 |     def __init__(
167 |         self,
168 |         points: Vect3Array = NULL_POINTS,
169 |         color: ManimColor = YELLOW,
170 |         radius: float = DEFAULT_GLOW_DOT_RADIUS,
171 |         glow_factor: float = 2.0,
172 |         **kwargs,
173 |     ):
174 |         super().__init__(
175 |             points,
176 |             color=color,
177 |             radius=radius,
178 |             glow_factor=glow_factor,
179 |             **kwargs,
180 |         )
181 | 
182 | 
183 | class GlowDot(GlowDots):
184 |     def __init__(self, center: Vect3 = ORIGIN, **kwargs):
185 |         super().__init__(points=np.array([center]), **kwargs)
186 | 


--------------------------------------------------------------------------------
/manimlib/mobject/types/image_mobject.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import numpy as np
 4 | import moderngl
 5 | from PIL import Image
 6 | 
 7 | from manimlib.constants import DL, DR, UL, UR
 8 | from manimlib.mobject.mobject import Mobject
 9 | from manimlib.utils.bezier import inverse_interpolate
10 | from manimlib.utils.images import get_full_raster_image_path
11 | from manimlib.utils.iterables import listify
12 | from manimlib.utils.iterables import resize_with_interpolation
13 | 
14 | from typing import TYPE_CHECKING
15 | 
16 | if TYPE_CHECKING:
17 |     from typing import Sequence, Tuple
18 |     from manimlib.typing import Vect3
19 | 
20 | 
21 | class ImageMobject(Mobject):
22 |     shader_folder: str = "image"
23 |     data_dtype: Sequence[Tuple[str, type, Tuple[int]]] = [
24 |         ('point', np.float32, (3,)),
25 |         ('im_coords', np.float32, (2,)),
26 |         ('opacity', np.float32, (1,)),
27 |     ]
28 |     render_primitive: int = moderngl.TRIANGLES
29 | 
30 |     def __init__(
31 |         self,
32 |         filename: str,
33 |         height: float = 4.0,
34 |         **kwargs
35 |     ):
36 |         self.height = height
37 |         self.image_path = get_full_raster_image_path(filename)
38 |         self.image = Image.open(self.image_path)
39 |         super().__init__(texture_paths={"Texture": self.image_path}, **kwargs)
40 | 
41 |     def init_data(self) -> None:
42 |         super().init_data(length=6)
43 |         self.data["point"][:] = [UL, DL, UR, DR, UR, DL]
44 |         self.data["im_coords"][:] = [(0, 0), (0, 1), (1, 0), (1, 1), (1, 0), (0, 1)]
45 |         self.data["opacity"][:] = self.opacity
46 | 
47 |     def init_points(self) -> None:
48 |         size = self.image.size
49 |         self.set_width(2 * size[0] / size[1], stretch=True)
50 |         self.set_height(self.height)
51 | 
52 |     @Mobject.affects_data
53 |     def set_opacity(self, opacity: float, recurse: bool = True):
54 |         self.data["opacity"][:, 0] = resize_with_interpolation(
55 |             np.array(listify(opacity)),
56 |             self.get_num_points()
57 |         )
58 |         return self
59 | 
60 |     def set_color(self, color, opacity=None, recurse=None):
61 |         return self
62 | 
63 |     def point_to_rgb(self, point: Vect3) -> Vect3:
64 |         x0, y0 = self.get_corner(UL)[:2]
65 |         x1, y1 = self.get_corner(DR)[:2]
66 |         x_alpha = inverse_interpolate(x0, x1, point[0])
67 |         y_alpha = inverse_interpolate(y0, y1, point[1])
68 |         if not (0 <= x_alpha <= 1) and (0 <= y_alpha <= 1):
69 |             # TODO, raise smarter exception
70 |             raise Exception("Cannot sample color from outside an image")
71 | 
72 |         pw, ph = self.image.size
73 |         rgb = self.image.getpixel((
74 |             int((pw - 1) * x_alpha),
75 |             int((ph - 1) * y_alpha),
76 |         ))[:3]
77 |         return np.array(rgb) / 255
78 | 


--------------------------------------------------------------------------------
/manimlib/mobject/types/point_cloud_mobject.py:
--------------------------------------------------------------------------------
  1 | from __future__ import annotations
  2 | 
  3 | import numpy as np
  4 | 
  5 | from manimlib.mobject.mobject import Mobject
  6 | from manimlib.utils.color import color_gradient
  7 | from manimlib.utils.color import color_to_rgba
  8 | from manimlib.utils.iterables import resize_with_interpolation
  9 | 
 10 | from typing import TYPE_CHECKING
 11 | 
 12 | if TYPE_CHECKING:
 13 |     from typing import Callable
 14 |     from manimlib.typing import ManimColor, Vect3, Vect3Array, Vect4Array, Self
 15 | 
 16 | 
 17 | class PMobject(Mobject):
 18 |     def set_points(self, points: Vect3Array):
 19 |         if len(points) == 0:
 20 |             points = np.zeros((0, 3))
 21 |         super().set_points(points)
 22 |         self.resize_points(len(points))
 23 |         return self
 24 | 
 25 |     def add_points(
 26 |         self,
 27 |         points: Vect3Array,
 28 |         rgbas: Vect4Array | None = None,
 29 |         color: ManimColor | None = None,
 30 |         opacity: float | None = None
 31 |     ) -> Self:
 32 |         """
 33 |         points must be a Nx3 numpy array, as must rgbas if it is not None
 34 |         """
 35 |         self.append_points(points)
 36 |         # rgbas array will have been resized with points
 37 |         if color is not None:
 38 |             if opacity is None:
 39 |                 opacity = self.data["rgba"][-1, 3]
 40 |             rgbas = np.repeat(
 41 |                 [color_to_rgba(color, opacity)],
 42 |                 len(points),
 43 |                 axis=0
 44 |             )
 45 |         if rgbas is not None:
 46 |             self.data["rgba"][-len(rgbas):] = rgbas
 47 |         return self
 48 | 
 49 |     def add_point(self, point: Vect3, rgba=None, color=None, opacity=None) -> Self:
 50 |         rgbas = None if rgba is None else [rgba]
 51 |         self.add_points([point], rgbas, color, opacity)
 52 |         return self
 53 | 
 54 |     @Mobject.affects_data
 55 |     def set_color_by_gradient(self, *colors: ManimColor) -> Self:
 56 |         self.data["rgba"][:] = np.array(list(map(
 57 |             color_to_rgba,
 58 |             color_gradient(colors, self.get_num_points())
 59 |         )))
 60 |         return self
 61 | 
 62 |     @Mobject.affects_data
 63 |     def match_colors(self, pmobject: PMobject) -> Self:
 64 |         self.data["rgba"][:] = resize_with_interpolation(
 65 |             pmobject.data["rgba"], self.get_num_points()
 66 |         )
 67 |         return self
 68 | 
 69 |     @Mobject.affects_data
 70 |     def filter_out(self, condition: Callable[[np.ndarray], bool]) -> Self:
 71 |         for mob in self.family_members_with_points():
 72 |             mob.data = mob.data[~np.apply_along_axis(condition, 1, mob.get_points())]
 73 |         return self
 74 | 
 75 |     @Mobject.affects_data
 76 |     def sort_points(self, function: Callable[[Vect3], None] = lambda p: p[0]) -> Self:
 77 |         """
 78 |         function is any map from R^3 to R
 79 |         """
 80 |         for mob in self.family_members_with_points():
 81 |             indices = np.argsort(
 82 |                 np.apply_along_axis(function, 1, mob.get_points())
 83 |             )
 84 |             mob.data[:] = mob.data[indices]
 85 |         return self
 86 | 
 87 |     @Mobject.affects_data
 88 |     def ingest_submobjects(self) -> Self:
 89 |         self.data = np.vstack([
 90 |             sm.data for sm in self.get_family()
 91 |         ])
 92 |         return self
 93 | 
 94 |     def point_from_proportion(self, alpha: float) -> np.ndarray:
 95 |         index = alpha * (self.get_num_points() - 1)
 96 |         return self.get_points()[int(index)]
 97 | 
 98 |     @Mobject.affects_data
 99 |     def pointwise_become_partial(self, pmobject: PMobject, a: float, b: float) -> Self:
100 |         lower_index = int(a * pmobject.get_num_points())
101 |         upper_index = int(b * pmobject.get_num_points())
102 |         self.data = pmobject.data[lower_index:upper_index].copy()
103 |         return self
104 | 
105 | 
106 | class PGroup(PMobject):
107 |     def __init__(self, *pmobs: PMobject, **kwargs):
108 |         if not all([isinstance(m, PMobject) for m in pmobs]):
109 |             raise Exception("All submobjects must be of type PMobject")
110 |         super().__init__(**kwargs)
111 |         self.add(*pmobs)
112 | 


--------------------------------------------------------------------------------
/manimlib/mobject/value_tracker.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import numpy as np
 4 | from manimlib.mobject.mobject import Mobject
 5 | from manimlib.utils.iterables import listify
 6 | 
 7 | from typing import TYPE_CHECKING
 8 | 
 9 | if TYPE_CHECKING:
10 |     from manimlib.typing import Self
11 | 
12 | 
13 | class ValueTracker(Mobject):
14 |     """
15 |     Not meant to be displayed.  Instead the position encodes some
16 |     number, often one which another animation or continual_animation
17 |     uses for its update function, and by treating it as a mobject it can
18 |     still be animated and manipulated just like anything else.
19 |     """
20 |     value_type: type = np.float64
21 | 
22 |     def __init__(
23 |         self,
24 |         value: float | complex | np.ndarray = 0,
25 |         **kwargs
26 |     ):
27 |         self.value = value
28 |         super().__init__(**kwargs)
29 | 
30 |     def init_uniforms(self) -> None:
31 |         super().init_uniforms()
32 |         self.uniforms["value"] = np.array(
33 |             listify(self.value),
34 |             dtype=self.value_type,
35 |         )
36 | 
37 |     def get_value(self) -> float | complex | np.ndarray:
38 |         result = self.uniforms["value"]
39 |         if len(result) == 1:
40 |             return result[0]
41 |         return result
42 | 
43 |     def set_value(self, value: float | complex | np.ndarray) -> Self:
44 |         self.uniforms["value"][:] = value
45 |         return self
46 | 
47 |     def increment_value(self, d_value: float | complex) -> None:
48 |         self.set_value(self.get_value() + d_value)
49 | 
50 | 
51 | class ExponentialValueTracker(ValueTracker):
52 |     """
53 |     Operates just like ValueTracker, except it encodes the value as the
54 |     exponential of a position coordinate, which changes how interpolation
55 |     behaves
56 |     """
57 | 
58 |     def get_value(self) -> float | complex:
59 |         return np.exp(ValueTracker.get_value(self))
60 | 
61 |     def set_value(self, value: float | complex):
62 |         return ValueTracker.set_value(self, np.log(value))
63 | 
64 | 
65 | class ComplexValueTracker(ValueTracker):
66 |     value_type: type = np.complex128
67 | 


--------------------------------------------------------------------------------
/manimlib/scene/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3b1b/manim/41613db7eca9eabbb40aa830631e5206c9282f0f/manimlib/scene/__init__.py


--------------------------------------------------------------------------------
/manimlib/shaders/image/frag.glsl:
--------------------------------------------------------------------------------
 1 | #version 330
 2 | 
 3 | uniform sampler2D Texture;
 4 | 
 5 | in vec2 v_im_coords;
 6 | in float v_opacity;
 7 | 
 8 | out vec4 frag_color;
 9 | 
10 | void main() {
11 |     frag_color = texture(Texture, v_im_coords);
12 |     frag_color.a *= v_opacity;
13 | }


--------------------------------------------------------------------------------
/manimlib/shaders/image/vert.glsl:
--------------------------------------------------------------------------------
 1 | #version 330
 2 | 
 3 | uniform sampler2D Texture;
 4 | 
 5 | in vec3 point;
 6 | in vec2 im_coords;
 7 | in float opacity;
 8 | 
 9 | out vec2 v_im_coords;
10 | out float v_opacity;
11 | 
12 | // Analog of import for manim only
13 | #INSERT emit_gl_Position.glsl
14 | 
15 | void main(){
16 |     v_im_coords = im_coords;
17 |     v_opacity = opacity;
18 |     emit_gl_Position(point);
19 | }


--------------------------------------------------------------------------------
/manimlib/shaders/inserts/NOTE.md:
--------------------------------------------------------------------------------
1 | There seems to be no analog to #include in C++ for OpenGL shaders.  While there are other options for sharing code between shaders, a lot of them aren't great, especially if the goal is to have all the logic for which specific bits of code to share handled in the shader file itself.  So the way manim currently works is to replace any line which looks like 
2 | 
3 | #INSERT <file_name>
4 | 
5 | with the code from one of the files in this folder.
6 | 
7 | The functions in this file may include declarations of uniforms, so one should not re-declare those in the surrounding context.
8 | 


--------------------------------------------------------------------------------
/manimlib/shaders/inserts/complex_functions.glsl:
--------------------------------------------------------------------------------
 1 | vec2 complex_mult(vec2 z, vec2 w){
 2 |     return vec2(z.x * w.x - z.y * w.y, z.x * w.y + z.y * w.x);
 3 | }
 4 | 
 5 | vec2 complex_div(vec2 z, vec2 w){
 6 |     return complex_mult(z, vec2(w.x, -w.y)) / (w.x * w.x + w.y * w.y);
 7 | }
 8 | 
 9 | vec2 complex_pow(vec2 z, int n){
10 |     vec2 result = vec2(1.0, 0.0);
11 |     for(int i = 0; i < n; i++){
12 |         result = complex_mult(result, z);
13 |     }
14 |     return result;
15 | }


--------------------------------------------------------------------------------
/manimlib/shaders/inserts/emit_gl_Position.glsl:
--------------------------------------------------------------------------------
 1 | uniform float is_fixed_in_frame;
 2 | uniform mat4 view;
 3 | uniform float focal_distance;
 4 | uniform vec3 frame_rescale_factors;
 5 | uniform vec4 clip_plane;
 6 | 
 7 | void emit_gl_Position(vec3 point){
 8 |     vec4 result = vec4(point, 1.0);
 9 |     // This allows for smooth transitions between objects fixed and unfixed from frame
10 |     result = mix(view * result, result, is_fixed_in_frame);
11 |     // Essentially a projection matrix
12 |     result.xyz *= frame_rescale_factors;
13 |     result.w = 1.0 - result.z;
14 |     // Flip and scale to prevent premature clipping
15 |     result.z *= -0.1;
16 |     gl_Position = result;
17 |     
18 |     if(clip_plane.xyz != vec3(0.0, 0.0, 0.0)){
19 |         gl_ClipDistance[0] = dot(vec4(point, 1.0), clip_plane);
20 |     }
21 | }
22 | 


--------------------------------------------------------------------------------
/manimlib/shaders/inserts/finalize_color.glsl:
--------------------------------------------------------------------------------
 1 | uniform vec3 light_position;
 2 | uniform vec3 camera_position;
 3 | uniform vec3 shading;
 4 | 
 5 | vec3 float_to_color(float value, float min_val, float max_val, vec3[9] colormap_data){
 6 |     float alpha = clamp((value - min_val) / (max_val - min_val), 0.0, 1.0);
 7 |     int disc_alpha = min(int(alpha * 8), 7);
 8 |     return mix(
 9 |         colormap_data[disc_alpha],
10 |         colormap_data[disc_alpha + 1],
11 |         8.0 * alpha - disc_alpha
12 |     );
13 | }
14 | 
15 | 
16 | vec4 add_light(vec4 color, vec3 point, vec3 unit_normal){
17 |     if(shading == vec3(0.0)) return color;
18 | 
19 |     float reflectiveness = shading.x;
20 |     float gloss = shading.y;
21 |     float shadow = shading.z;
22 | 
23 |     vec4 result = color;
24 |     vec3 to_camera = normalize(camera_position - point);
25 |     vec3 to_light = normalize(light_position - point);
26 | 
27 |     float light_to_normal = dot(to_light, unit_normal);
28 |     // When unit normal points towards light, brighten
29 |     float bright_factor = max(light_to_normal, 0) * reflectiveness;
30 |     // For glossy surface, add extra shine if light beam goes towards camera
31 |     vec3 light_reflection = reflect(-to_light, unit_normal);
32 |     float light_to_cam = dot(light_reflection, to_camera);
33 |     float shine = gloss * exp(-3 * pow(1 - light_to_cam, 2));
34 |     bright_factor += shine;
35 | 
36 |     result.rgb = mix(result.rgb, vec3(1.0), bright_factor);
37 |     if (light_to_normal < 0){
38 |         // Darken
39 |         result.rgb = mix(
40 |             result.rgb,
41 |             vec3(0.0),
42 |             max(-light_to_normal, 0) * shadow
43 |         );
44 |     }
45 |     return result;
46 | }
47 | 
48 | vec4 finalize_color(vec4 color, vec3 point, vec3 unit_normal){
49 |     ///// INSERT COLOR FUNCTION HERE /////
50 |     // The line above may be replaced by arbitrary code snippets, as per
51 |     // the method Mobject.set_color_by_code
52 |     return add_light(color, point, unit_normal);
53 | }


--------------------------------------------------------------------------------
/manimlib/shaders/inserts/get_unit_normal.glsl:
--------------------------------------------------------------------------------
 1 | vec3 get_unit_normal(vec3 p0, vec3 p1, vec3 p2){
 2 |     float tol = 1e-6;
 3 |     vec3 v1 = normalize(p1 - p0);
 4 |     vec3 v2 = normalize(p2 - p0);
 5 |     vec3 cp = cross(v1, v2);
 6 |     float cp_norm = length(cp);
 7 | 
 8 |     if(cp_norm > tol) return cp / cp_norm;
 9 | 
10 |     // Otherwise, three pionts form a line, so find
11 |     // a normal vector to that line in the plane shared
12 |     // with the z-axis
13 |     vec3 comb = v1 + v2;
14 |     cp = cross(cross(comb, vec3(0.0, 0.0, 1.0)), comb);
15 |     cp_norm = length(cp);
16 |     if(cp_norm > tol) return cp / cp_norm;
17 | 
18 |     // Otherwise, the points line up with the z-axis.
19 |     return vec3(0.0, -1.0, 0.0);
20 | }


--------------------------------------------------------------------------------
/manimlib/shaders/inserts/get_xyz_to_uv.glsl:
--------------------------------------------------------------------------------
  1 | vec2 xs_on_clean_parabola(vec3 b0, vec3 b1, vec3 b2){
  2 |     /*
  3 |     Given three control points for a quadratic bezier,
  4 |     this returns the two values (x0, x2) such that the
  5 |     section of the parabola y = x^2 between those values
  6 |     is isometric to the given quadratic bezier.
  7 | 
  8 |     Adapated from https://raphlinus.github.io/graphics/curves/2019/12/23/flatten-quadbez.html
  9 |     */
 10 |     vec3 dd = 2 * b1 - b0 - b2;
 11 | 
 12 |     float u0 = dot(b1 - b0, dd);
 13 |     float u2 = dot(b2 - b1, dd);
 14 |     float cp = length(cross(b2 - b0, dd));
 15 | 
 16 |     return vec2(u0 / cp, u2 / cp);
 17 | }
 18 | 
 19 | 
 20 | mat4 map_triangles(vec3 src0, vec3 src1, vec3 src2, vec3 dst0, vec3 dst1, vec3 dst2){
 21 |     /*
 22 |     Return an affine transform which maps the triangle (src0, src1, src2)
 23 |     onto the triangle (dst0, dst1, dst2)
 24 |     */
 25 |     mat4 src_mat = mat4(
 26 |         src0, 1.0,
 27 |         src1, 1.0,
 28 |         src2, 1.0,
 29 |         vec4(1.0)
 30 |     );
 31 |     mat4 dst_mat = mat4(
 32 |         dst0, 1.0,
 33 |         dst1, 1.0,
 34 |         dst2, 1.0,
 35 |         vec4(1.0)
 36 |     );
 37 |     return dst_mat * inverse(src_mat);
 38 | }
 39 | 
 40 | 
 41 | mat4 rotation(vec3 axis, float cos_angle){
 42 |     float c = cos_angle;
 43 |     float s = sqrt(1 - c * c);  // Sine of the angle
 44 |     float oc = 1.0 - c;
 45 |     float ax = axis.x;
 46 |     float ay = axis.y;
 47 |     float az = axis.z;
 48 | 
 49 |     return mat4(
 50 |         oc * ax * ax + c,      oc * ax * ay + az * s, oc * az * ax - ay * s, 0.0,
 51 |         oc * ax * ay - az * s, oc * ay * ay + c,      oc * ay * az + ax * s, 0.0,
 52 |         oc * az * ax + ay * s, oc * ay * az - ax * s, oc * az * az + c,      0.0,
 53 |         0.0, 0.0, 0.0, 1.0
 54 |     );
 55 | }
 56 | 
 57 | 
 58 | mat4 map_onto_x_axis(vec3 src0, vec3 src1){
 59 |     mat4 shift = mat4(1.0);
 60 |     shift[3].xyz = -src0;
 61 | 
 62 |     // Find rotation matrix between unit vectors in each direction    
 63 |     vec3 vect = normalize(src1 - src0);
 64 |     // No rotation needed
 65 |     if(vect.x > 1 - 1e-6) return shift;
 66 | 
 67 |     // Equivalent to cross(vect, vec3(1, 0, 0))
 68 |     vec3 axis = normalize(vec3(0.0, vect.z, -vect.y));
 69 |     mat4 rotate = rotation(axis, vect.x);
 70 |     return rotate * shift;
 71 | }
 72 | 
 73 | 
 74 | mat4 get_xyz_to_uv(
 75 |     vec3 b0, vec3 b1, vec3 b2,
 76 |     float threshold,
 77 |     out bool exceeds_threshold
 78 | ){
 79 |     /*
 80 |     Populates the matrix `result` with an affine transformation which maps a set of
 81 |     quadratic bezier controls points into a new coordinate system such that the bezier
 82 |     curve coincides with y = x^2.
 83 | 
 84 |     If the x-range under this part of the curve exceeds `threshold`, this returns false
 85 |     and populates result a matrix mapping b0 and b2 onto the x-axis
 86 |     */
 87 |     vec2 xs = xs_on_clean_parabola(b0, b1, b2);
 88 |     float x0 = xs[0];
 89 |     float x1 = 0.5 * (xs[0] + xs[1]);
 90 |     float x2 = xs[1];
 91 |     // Portions of the parabola y = x^2 where abs(x) exceeds
 92 |     // this value are treated as straight lines.
 93 |     exceeds_threshold = (min(x0, x2) > threshold || max(x0, x2) < -threshold);
 94 |     if(exceeds_threshold){
 95 |         return map_onto_x_axis(b0, b2);
 96 |     }
 97 |     // This triangle on the xy plane should be isometric
 98 |     // to (b0, b1, b2), and it should define a quadratic
 99 |     // bezier segment aligned with y = x^2
100 |     vec3 dst0 = vec3(x0, x0 * x0, 0.0);
101 |     vec3 dst1 = vec3(x1, x0 * x2, 0.0);
102 |     vec3 dst2 = vec3(x2, x2 * x2, 0.0);
103 |     return map_triangles(b0, b1, b2, dst0, dst1, dst2);
104 | }
105 | 


--------------------------------------------------------------------------------
/manimlib/shaders/mandelbrot_fractal/frag.glsl:
--------------------------------------------------------------------------------
 1 | #version 330
 2 | 
 3 | uniform vec2 parameter;
 4 | uniform float opacity;
 5 | uniform float n_steps;
 6 | uniform float mandelbrot;
 7 | 
 8 | uniform vec3 color0;
 9 | uniform vec3 color1;
10 | uniform vec3 color2;
11 | uniform vec3 color3;
12 | uniform vec3 color4;
13 | uniform vec3 color5;
14 | uniform vec3 color6;
15 | uniform vec3 color7;
16 | uniform vec3 color8;
17 | 
18 | in vec3 xyz_coords;
19 | 
20 | out vec4 frag_color;
21 | 
22 | #INSERT finalize_color.glsl
23 | #INSERT complex_functions.glsl
24 | 
25 | const int MAX_DEGREE = 5;
26 | 
27 | void main() {
28 |     vec3 color_map[9] = vec3[9](
29 |         color0, color1, color2, color3,
30 |         color4, color5, color6, color7, color8
31 |     );
32 |     vec3 color;
33 | 
34 |     vec2 z;
35 |     vec2 c;
36 | 
37 |     if(bool(mandelbrot)){
38 |         c = xyz_coords.xy;
39 |         z = vec2(0.0, 0.0);
40 |     }else{
41 |         c = parameter;
42 |         z = xyz_coords.xy;
43 |     }
44 | 
45 |     float outer_bound = 2.0;
46 |     bool stable = true;
47 |     for(int n = 0; n < int(n_steps); n++){
48 |         z = complex_mult(z, z) + c;
49 |         if(length(z) > outer_bound){
50 |             float float_n = float(n);
51 |             float_n += log(outer_bound) / log(length(z));
52 |             float_n += 0.5 * length(c);
53 |             color = float_to_color(sqrt(float_n), 1.5, 8.0, color_map);
54 |             stable = false;
55 |             break;
56 |         }
57 |     }
58 |     if(stable){
59 |         color = vec3(0.0, 0.0, 0.0);
60 |     }
61 | 
62 |     frag_color = finalize_color(
63 |         vec4(color, opacity),
64 |         xyz_coords,
65 |         vec3(0.0, 0.0, 1.0)
66 |     );
67 |  }


--------------------------------------------------------------------------------
/manimlib/shaders/mandelbrot_fractal/vert.glsl:
--------------------------------------------------------------------------------
 1 | #version 330
 2 | 
 3 | in vec3 point;
 4 | out vec3 xyz_coords;
 5 | 
 6 | uniform float scale_factor;
 7 | uniform vec3 offset;
 8 | 
 9 | #INSERT emit_gl_Position.glsl
10 | 
11 | void main(){
12 |     xyz_coords = (point - offset) / scale_factor;
13 |     emit_gl_Position(point);
14 | }


--------------------------------------------------------------------------------
/manimlib/shaders/newton_fractal/frag.glsl:
--------------------------------------------------------------------------------
  1 | #version 330
  2 | 
  3 | uniform vec4 color0;
  4 | uniform vec4 color1;
  5 | uniform vec4 color2;
  6 | uniform vec4 color3;
  7 | uniform vec4 color4;
  8 | 
  9 | uniform vec2 coef0;
 10 | uniform vec2 coef1;
 11 | uniform vec2 coef2;
 12 | uniform vec2 coef3;
 13 | uniform vec2 coef4;
 14 | uniform vec2 coef5;
 15 | 
 16 | uniform vec2 root0;
 17 | uniform vec2 root1;
 18 | uniform vec2 root2;
 19 | uniform vec2 root3;
 20 | uniform vec2 root4;
 21 | 
 22 | uniform float n_roots;
 23 | uniform float n_steps;
 24 | uniform float julia_highlight;
 25 | uniform float saturation_factor;
 26 | uniform float black_for_cycles;
 27 | uniform float is_parameter_space;
 28 | 
 29 | in vec3 xyz_coords;
 30 | 
 31 | out vec4 frag_color;
 32 | 
 33 | #INSERT finalize_color.glsl
 34 | #INSERT complex_functions.glsl
 35 | 
 36 | const int MAX_DEGREE = 5;
 37 | const float CLOSE_ENOUGH = 1e-3;
 38 | 
 39 | 
 40 | vec2 poly(vec2 z, vec2[MAX_DEGREE + 1] coefs){
 41 |     vec2 result = vec2(0.0);
 42 |     for(int n = 0; n < int(n_roots) + 1; n++){
 43 |         result += complex_mult(coefs[n], complex_pow(z, n));
 44 |     }
 45 |     return result;
 46 | }
 47 | 
 48 | vec2 dpoly(vec2 z, vec2[MAX_DEGREE + 1] coefs){
 49 |     vec2 result = vec2(0.0);
 50 |     for(int n = 1; n < int(n_roots) + 1; n++){
 51 |         result += n * complex_mult(coefs[n], complex_pow(z, n - 1));
 52 |     }
 53 |     return result;
 54 | }
 55 | 
 56 | vec2 seek_root(vec2 z, vec2[MAX_DEGREE + 1] coefs, int max_steps, out float n_iters){
 57 |     float last_len;
 58 |     float curr_len;
 59 |     float threshold = CLOSE_ENOUGH;
 60 | 
 61 |     for(int i = 0; i < max_steps; i++){
 62 |         last_len = curr_len;
 63 |         n_iters = float(i);
 64 |         vec2 step = complex_div(poly(z, coefs), dpoly(z, coefs));
 65 |         curr_len = length(step);
 66 |         if(curr_len < threshold){
 67 |             break;
 68 |         }
 69 |         z = z - step;
 70 |     }
 71 |     n_iters -= log(curr_len) / log(threshold);
 72 | 
 73 |     return z;
 74 | }
 75 | 
 76 | 
 77 | void main() {
 78 |     vec2[MAX_DEGREE + 1] coefs = vec2[MAX_DEGREE + 1](coef0, coef1, coef2, coef3, coef4, coef5);
 79 |     vec2[MAX_DEGREE] roots = vec2[MAX_DEGREE](root0, root1, root2, root3, root4);
 80 |     vec4[MAX_DEGREE] colors = vec4[MAX_DEGREE](color0, color1, color2, color3, color4);
 81 | 
 82 |     vec2 z = xyz_coords.xy;
 83 | 
 84 |     if(is_parameter_space > 0){
 85 |         // In this case, pixel should correspond to one of the roots
 86 |         roots[2] = xyz_coords.xy;
 87 |         vec2 r0 = roots[0];
 88 |         vec2 r1 = roots[1];
 89 |         vec2 r2 = roots[2];
 90 | 
 91 |         // It is assumed that the polynomial is cubid...
 92 |         coefs[0] = -complex_mult(complex_mult(r0, r1), r2);
 93 |         coefs[1] = complex_mult(r0, r1) + complex_mult(r0, r2) + complex_mult(r1, r2);
 94 |         coefs[2] = -(r0 + r1 + r2);
 95 |         coefs[3] = vec2(1.0, 0.0);
 96 | 
 97 |         // Seed value is always center of the roots
 98 |         z = -coefs[2] / 3.0;
 99 |     }
100 | 
101 |     float n_iters;
102 |     vec2 found_root = seek_root(z, coefs, int(n_steps), n_iters);
103 | 
104 |     vec4 color = vec4(0.0);
105 |     float min_dist = 1e10;
106 |     float dist;
107 |     for(int i = 0; i < int(n_roots); i++){
108 |         dist = distance(roots[i], found_root);
109 |         if(dist < min_dist){
110 |             min_dist = dist;
111 |             color = colors[i];
112 |         }
113 |     }
114 |     color *= 1.0 + (0.01 * saturation_factor) * (n_iters - 2 * saturation_factor);
115 | 
116 |     if(black_for_cycles > 0 && min_dist > CLOSE_ENOUGH){
117 |         color = vec4(0.0, 0.0, 0.0, 1.0);
118 |     }
119 | 
120 |     if(julia_highlight > 0.0){
121 |         float radius = julia_highlight;
122 |         vec2[4] samples = vec2[4](
123 |             z + vec2(radius, 0.0),
124 |             z + vec2(-radius, 0.0),
125 |             z + vec2(0.0, radius),
126 |             z + vec2(0.0, -radius)
127 |         );
128 |         for(int i = 0; i < 4; i++){
129 |             for(int j = 0; j < n_steps; j++){
130 |                 vec2 z = samples[i];
131 |                 z = z - complex_div(poly(z, coefs), dpoly(z, coefs));
132 |                 samples[i] = z;
133 |             }
134 |         }
135 |         float max_dist = 0.0;
136 |         for(int i = 0; i < 4; i++){
137 |             max_dist = max(max_dist, distance(samples[i], samples[(i + 1) % 4]));
138 |         }
139 |         color *= 1.0 * smoothstep(0, 0.1, max_dist);
140 |     }
141 | 
142 |     frag_color = finalize_color(
143 |         color,
144 |         xyz_coords,
145 |         vec3(0.0, 0.0, 1.0)
146 |     );
147 |  }


--------------------------------------------------------------------------------
/manimlib/shaders/newton_fractal/vert.glsl:
--------------------------------------------------------------------------------
 1 | #version 330
 2 | 
 3 | in vec3 point;
 4 | out vec3 xyz_coords;
 5 | 
 6 | uniform float scale_factor;
 7 | uniform vec3 offset;
 8 | 
 9 | #INSERT emit_gl_Position.glsl
10 | 
11 | void main(){
12 |     xyz_coords = (point - offset) / scale_factor;
13 |     emit_gl_Position(point);
14 | }


--------------------------------------------------------------------------------
/manimlib/shaders/quadratic_bezier/depth/frag.glsl:
--------------------------------------------------------------------------------
1 | #version 330
2 | 
3 | out float frag_depth;
4 | 
5 | void main() {
6 |     frag_depth = gl_FragCoord.z;
7 | }
8 | 


--------------------------------------------------------------------------------
/manimlib/shaders/quadratic_bezier/depth/geom.glsl:
--------------------------------------------------------------------------------
 1 | #version 330
 2 | 
 3 | layout (triangles) in;
 4 | layout (triangle_strip, max_vertices = 6) out;
 5 | 
 6 | in vec3 verts[3];
 7 | in vec3 v_base_point[3];
 8 | 
 9 | out float depth;
10 | 
11 | #INSERT emit_gl_Position.glsl
12 | 
13 | 
14 | void emit_triangle(vec3 points[3]){
15 |     for(int i = 0; i < 3; i++){
16 |         emit_gl_Position(points[i]);
17 |         EmitVertex();
18 |     }
19 |     EndPrimitive();
20 | }
21 | 
22 | 
23 | void main(){
24 |     // Curves are marked as ended when the handle after
25 |     // the first anchor is set equal to that anchor
26 |     if (verts[0] == verts[1]) return;
27 | 
28 |     // Emit two triangles
29 |     emit_triangle(vec3[3](v_base_point[0], verts[0], verts[2]));
30 |     emit_triangle(vec3[3](verts[0], verts[1], verts[2]));
31 | }
32 | 
33 | 


--------------------------------------------------------------------------------
/manimlib/shaders/quadratic_bezier/depth/vert.glsl:
--------------------------------------------------------------------------------
 1 | #version 330
 2 | 
 3 | in vec3 point;
 4 | in vec3 base_normal;
 5 | 
 6 | out vec3 verts;
 7 | out vec3 v_base_point;
 8 | 
 9 | void main(){
10 |     verts = point;
11 |     v_base_point = base_normal;
12 | }


--------------------------------------------------------------------------------
/manimlib/shaders/quadratic_bezier/fill/frag.glsl:
--------------------------------------------------------------------------------
 1 | #version 330
 2 | 
 3 | uniform bool winding;
 4 | 
 5 | in vec4 color;
 6 | in float fill_all;
 7 | in float orientation;
 8 | in vec2 uv_coords;
 9 | 
10 | out vec4 frag_color;
11 | 
12 | void main() {
13 |     if (color.a == 0) discard;
14 |     frag_color = color;
15 |     /*
16 |     We want negatively oriented triangles to be canceled with positively
17 |     oriented ones. The easiest way to do this is to give them negative alpha,
18 |     and change the blend function to just add them. However, this messes with
19 |     usual blending, so instead the following line is meant to let this canceling
20 |     work even for the normal blending equation:
21 | 
22 |     (1 - alpha) * dst + alpha * src
23 | 
24 |     We want the effect of blending with a positively oriented triangle followed
25 |     by a negatively oriented one to return to whatever the original frag value
26 |     was. You can work out this will work if the alpha for negative orientations
27 |     is changed to -alpha / (1 - alpha). This has a singularity at alpha = 1,
28 |     so we cap it at a value very close to 1. Effectively, the purpose of this
29 |     cap is to make sure the original fragment color can be recovered even after
30 |     blending with an (alpha = 1) color.
31 |     */
32 |     float a = 0.95 * frag_color.a;
33 |     if(orientation < 0) a = -a / (1 - a);
34 |     frag_color.a = a;
35 | 
36 |     if (bool(fill_all)) return;
37 | 
38 |     float x = uv_coords.x;
39 |     float y = uv_coords.y;
40 |     float Fxy = (y - x * x);
41 |     if(Fxy < 0) discard;
42 | }
43 | 


--------------------------------------------------------------------------------
/manimlib/shaders/quadratic_bezier/fill/geom.glsl:
--------------------------------------------------------------------------------
 1 | #version 330
 2 | 
 3 | layout (triangles) in;
 4 | layout (triangle_strip, max_vertices = 6) out;
 5 | 
 6 | in vec3 verts[3];
 7 | in vec4 v_color[3];
 8 | in vec3 v_base_normal[3];
 9 | 
10 | out vec4 color;
11 | out float fill_all;
12 | out float orientation;
13 | // uv space is where the curve coincides with y = x^2
14 | out vec2 uv_coords;
15 | 
16 | // A quadratic bezier curve with these points coincides with y = x^2
17 | const vec2 SIMPLE_QUADRATIC[3] = vec2[3](
18 |     vec2(0.0, 0.0),
19 |     vec2(0.5, 0),
20 |     vec2(1.0, 1.0)
21 | );
22 | 
23 | // Analog of import for manim only
24 | #INSERT emit_gl_Position.glsl
25 | #INSERT finalize_color.glsl
26 | 
27 | 
28 | void emit_triangle(vec3 points[3], vec4 v_color[3], vec3 unit_normal){
29 |     orientation = sign(determinant(mat3(
30 |         unit_normal,
31 |         points[1] - points[0],
32 |         points[2] - points[0]
33 |     )));
34 | 
35 |     for(int i = 0; i < 3; i++){
36 |         uv_coords = SIMPLE_QUADRATIC[i];
37 |         color = finalize_color(v_color[i], points[i], unit_normal);
38 |         emit_gl_Position(points[i]);
39 |         EmitVertex();
40 |     }
41 |     EndPrimitive();
42 | }
43 | 
44 | 
45 | void main(){
46 |     // Curves are marked as ended when the handle after
47 |     // the first anchor is set equal to that anchor
48 |     if (verts[0] == verts[1]) return;
49 | 
50 |     // Check zero fill
51 |     if (vec3(v_color[0].a, v_color[1].a, v_color[2].a) == vec3(0.0, 0.0, 0.0)) return;
52 | 
53 |     vec3 base_point = v_base_normal[0];
54 |     vec3 unit_normal = v_base_normal[1];
55 |     // Emit main triangle
56 |     fill_all = 1.0;
57 |     emit_triangle(
58 |         vec3[3](base_point, verts[0], verts[2]),
59 |         vec4[3](v_color[1], v_color[0], v_color[2]),
60 |         unit_normal
61 |     );
62 |     // Edge triangle
63 |     fill_all = 0.0;
64 |     emit_triangle(
65 |         vec3[3](verts[0], verts[1], verts[2]),
66 |         vec4[3](v_color[0], v_color[1], v_color[2]),
67 |         unit_normal
68 |     );
69 | }
70 | 
71 | 


--------------------------------------------------------------------------------
/manimlib/shaders/quadratic_bezier/fill/vert.glsl:
--------------------------------------------------------------------------------
 1 | #version 330
 2 | 
 3 | in vec3 point;
 4 | in vec4 fill_rgba;
 5 | in vec3 base_normal;
 6 | 
 7 | out vec3 verts;  // Bezier control point
 8 | out vec4 v_color;
 9 | out vec3 v_base_normal;
10 | 
11 | void main(){
12 |     verts = point;
13 |     v_color = fill_rgba;
14 |     v_base_normal = base_normal;
15 | }


--------------------------------------------------------------------------------
/manimlib/shaders/quadratic_bezier/stroke/frag.glsl:
--------------------------------------------------------------------------------
 1 | #version 330
 2 | 
 3 | // Distance to the curve, and half the curve width, both as
 4 | // a ratio of the antialias width
 5 | in float dist_to_aaw;
 6 | in float half_width_to_aaw;
 7 | in vec4 color;
 8 | 
 9 | out vec4 frag_color;
10 | 
11 | void main() {
12 |     frag_color = color;
13 |     // sdf for the region around the curve we wish to color.
14 |     float signed_dist_to_region = abs(dist_to_aaw) - half_width_to_aaw;
15 |     frag_color.a *= smoothstep(0.5, -0.5, signed_dist_to_region);
16 |     // This line is replaced in VShaderWrapper
17 |     // MODIFY FRAG COLOR
18 | }


--------------------------------------------------------------------------------
/manimlib/shaders/quadratic_bezier/stroke/vert.glsl:
--------------------------------------------------------------------------------
 1 | #version 330
 2 | 
 3 | uniform float frame_scale;
 4 | uniform float is_fixed_in_frame;
 5 | uniform float scale_stroke_with_zoom;
 6 | 
 7 | in vec3 point;
 8 | in vec4 stroke_rgba;
 9 | in float stroke_width;
10 | in float joint_angle;
11 | in vec3 unit_normal;
12 | 
13 | // Bezier control point
14 | out vec3 verts;
15 | 
16 | out vec4 v_color;
17 | out float v_stroke_width;
18 | out float v_joint_angle;
19 | out vec3 v_unit_normal;
20 | 
21 | const float STROKE_WIDTH_CONVERSION = 0.01;
22 | 
23 | void main(){
24 |     verts = point;
25 |     v_color = stroke_rgba;
26 |     v_stroke_width = STROKE_WIDTH_CONVERSION * stroke_width * mix(frame_scale, 1, scale_stroke_with_zoom);
27 |     v_joint_angle = joint_angle;
28 |     v_unit_normal = unit_normal;
29 | }


--------------------------------------------------------------------------------
/manimlib/shaders/simple_vert.glsl:
--------------------------------------------------------------------------------
1 | #version 330
2 | 
3 | in vec3 point;
4 | 
5 | #INSERT emit_gl_Position.glsl
6 | 
7 | void main(){
8 |     emit_gl_Position(point);
9 | }


--------------------------------------------------------------------------------
/manimlib/shaders/surface/frag.glsl:
--------------------------------------------------------------------------------
1 | #version 330
2 | 
3 | in vec4 v_color;
4 | out vec4 frag_color;
5 | 
6 | void main() {
7 |     frag_color = v_color;
8 | }


--------------------------------------------------------------------------------
/manimlib/shaders/surface/vert.glsl:
--------------------------------------------------------------------------------
 1 | #version 330
 2 | 
 3 | in vec3 point;
 4 | in vec3 d_normal_point;
 5 | in vec4 rgba;
 6 | 
 7 | out vec4 v_color;
 8 | 
 9 | #INSERT emit_gl_Position.glsl
10 | #INSERT get_unit_normal.glsl
11 | #INSERT finalize_color.glsl
12 | 
13 | const float EPSILON = 1e-10;
14 | 
15 | void main(){
16 |     emit_gl_Position(point);
17 |     vec3 unit_normal = normalize(d_normal_point - point);
18 |     v_color = finalize_color(rgba, point, unit_normal);
19 | }


--------------------------------------------------------------------------------
/manimlib/shaders/textured_surface/frag.glsl:
--------------------------------------------------------------------------------
 1 | #version 330
 2 | 
 3 | uniform sampler2D LightTexture;
 4 | uniform sampler2D DarkTexture;
 5 | uniform float num_textures;
 6 | 
 7 | in vec3 v_point;
 8 | in vec3 v_unit_normal;
 9 | in vec2 v_im_coords;
10 | in float v_opacity;
11 | 
12 | out vec4 frag_color;
13 | 
14 | #INSERT finalize_color.glsl
15 | 
16 | const float dark_shift = 0.2;
17 | 
18 | void main() {
19 |     vec4 color = texture(LightTexture, v_im_coords);
20 |     if(num_textures == 2.0){
21 |         vec4 dark_color = texture(DarkTexture, v_im_coords);
22 |         float dp = dot(
23 |             normalize(light_position - v_point),
24 |             v_unit_normal
25 |         );
26 |         float alpha = smoothstep(-dark_shift, dark_shift, dp);
27 |         color = mix(dark_color, color, alpha);
28 |     }
29 |     if (color.a == 0) discard;
30 | 
31 |     frag_color = finalize_color(
32 |         color,
33 |         v_point,
34 |         v_unit_normal
35 |     );
36 |     frag_color.a = v_opacity;
37 | }


--------------------------------------------------------------------------------
/manimlib/shaders/textured_surface/vert.glsl:
--------------------------------------------------------------------------------
 1 | #version 330
 2 | 
 3 | in vec3 point;
 4 | in vec3 d_normal_point;
 5 | in vec2 im_coords;
 6 | in float opacity;
 7 | 
 8 | out vec3 v_point;
 9 | out vec3 v_unit_normal;
10 | out vec2 v_im_coords;
11 | out float v_opacity;
12 | 
13 | uniform float is_sphere;
14 | uniform vec3 center;
15 | 
16 | #INSERT emit_gl_Position.glsl
17 | #INSERT get_unit_normal.glsl
18 | 
19 | const float EPSILON = 1e-10;
20 | 
21 | void main(){
22 |     v_point = point;
23 |     v_unit_normal = normalize(d_normal_point - point);;
24 |     v_im_coords = im_coords;
25 |     v_opacity = opacity;
26 |     emit_gl_Position(point);
27 | }


--------------------------------------------------------------------------------
/manimlib/shaders/true_dot/frag.glsl:
--------------------------------------------------------------------------------
 1 | #version 330
 2 | 
 3 | uniform float glow_factor;
 4 | uniform mat4 perspective;
 5 | 
 6 | in vec4 color;
 7 | in float scaled_aaw;
 8 | in vec3 point;
 9 | in vec3 to_cam;
10 | in vec3 center;
11 | in float radius;
12 | in vec2 uv_coords;
13 | 
14 | out vec4 frag_color;
15 | 
16 | // This includes a declaration of uniform vec3 shading
17 | #INSERT finalize_color.glsl
18 | 
19 | void main() {
20 |     float r = length(uv_coords.xy);
21 |     if(r > 1.0) discard;
22 | 
23 |     frag_color = color;
24 | 
25 |     if(glow_factor > 0){
26 |         frag_color.a *= pow(1 - r, glow_factor);
27 |     }
28 | 
29 |     if(shading != vec3(0.0)){
30 |         vec3 point_3d = point + radius * sqrt(1 - r * r) * to_cam;
31 |         vec3 normal = normalize(point_3d - center);
32 |         frag_color = finalize_color(frag_color, point_3d, normal);
33 |     }
34 | 
35 |     frag_color.a *= smoothstep(1.0, 1.0 - scaled_aaw, r);
36 | }


--------------------------------------------------------------------------------
/manimlib/shaders/true_dot/geom.glsl:
--------------------------------------------------------------------------------
 1 | #version 330
 2 | 
 3 | layout (points) in;
 4 | layout (triangle_strip, max_vertices = 4) out;
 5 | 
 6 | uniform float pixel_size;
 7 | uniform float anti_alias_width;
 8 | uniform float frame_scale;
 9 | uniform vec3 camera_position;
10 | 
11 | in vec3 v_point[1];
12 | in float v_radius[1];
13 | in vec4 v_rgba[1];
14 | 
15 | out vec4 color;
16 | out float scaled_aaw;
17 | out vec3 point;
18 | out vec3 to_cam;
19 | out vec3 center;
20 | out float radius;
21 | out vec2 uv_coords;
22 | 
23 | #INSERT emit_gl_Position.glsl
24 | 
25 | void main(){
26 |     color = v_rgba[0];
27 |     radius = v_radius[0];
28 |     center = v_point[0];
29 |     scaled_aaw = (anti_alias_width * pixel_size) / v_radius[0];
30 | 
31 |     to_cam = normalize(camera_position - v_point[0]);
32 |     vec3 right = v_radius[0] * normalize(cross(vec3(0, 1, 1), to_cam));
33 |     vec3 up = v_radius[0] * normalize(cross(to_cam, right));
34 | 
35 |     for(int i = -1; i < 2; i += 2){
36 |         for(int j = -1; j < 2; j += 2){
37 |             point = v_point[0] + i * right + j * up;
38 |             uv_coords = vec2(i, j);
39 |             emit_gl_Position(point);
40 |             EmitVertex();
41 |         }
42 |     }
43 |     EndPrimitive();
44 | }


--------------------------------------------------------------------------------
/manimlib/shaders/true_dot/vert.glsl:
--------------------------------------------------------------------------------
 1 | #version 330
 2 | 
 3 | in vec3 point;
 4 | in float radius;
 5 | in vec4 rgba;
 6 | 
 7 | out vec3 v_point;
 8 | out float v_radius;
 9 | out vec4 v_rgba;
10 | 
11 | 
12 | void main(){
13 |     v_point = point;
14 |     v_radius = radius;
15 |     v_rgba = rgba;
16 | }


--------------------------------------------------------------------------------
/manimlib/typing.py:
--------------------------------------------------------------------------------
 1 | from typing import TYPE_CHECKING
 2 | 
 3 | if TYPE_CHECKING:
 4 |     from typing import Union, Tuple, Annotated, Literal, Iterable, Dict
 5 |     from colour import Color
 6 |     import numpy as np
 7 |     import re
 8 | 
 9 |     try:
10 |         from typing_extensions import Self
11 |     except ImportError:
12 |         from typing import Self
13 | 
14 |     # Abbreviations for a common types
15 |     ManimColor = Union[str, Color, None]
16 |     RangeSpecifier = Tuple[float, float, float] | Tuple[float, float]
17 | 
18 | 
19 |     Span = tuple[int, int]
20 |     SingleSelector = Union[
21 |         str,
22 |         re.Pattern,
23 |         tuple[Union[int, None], Union[int, None]],
24 |     ]
25 |     Selector = Union[SingleSelector, Iterable[SingleSelector]]
26 | 
27 |     UniformDict = Dict[str, float | bool | np.ndarray | tuple]
28 | 
29 |     # These are various alternate names for np.ndarray meant to specify
30 |     # certain shapes.
31 |     #
32 |     # In theory, these annotations could be used to check arrays sizes
33 |     # at runtime, but at the moment nothing actually uses them, and
34 |     # the names are here primarily to enhance readibility and allow
35 |     # for some stronger type checking if numpy has stronger typing
36 |     # in the future
37 |     FloatArray = np.ndarray[int, np.dtype[np.float64]]
38 |     Vect2 = Annotated[FloatArray, Literal[2]]
39 |     Vect3 = Annotated[FloatArray, Literal[3]]
40 |     Vect4 = Annotated[FloatArray, Literal[4]]
41 |     VectN = Annotated[FloatArray, Literal["N"]]
42 |     Matrix3x3 = Annotated[FloatArray, Literal[3, 3]]
43 |     VectArray = Annotated[FloatArray, Literal["N", 1]]
44 |     Vect2Array = Annotated[FloatArray, Literal["N", 2]]
45 |     Vect3Array = Annotated[FloatArray, Literal["N", 3]]
46 |     Vect4Array = Annotated[FloatArray, Literal["N", 4]]
47 |     VectNArray = Annotated[FloatArray, Literal["N", "M"]]
48 | 


--------------------------------------------------------------------------------
/manimlib/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3b1b/manim/41613db7eca9eabbb40aa830631e5206c9282f0f/manimlib/utils/__init__.py


--------------------------------------------------------------------------------
/manimlib/utils/cache.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import os
 4 | from diskcache import Cache
 5 | from contextlib import contextmanager
 6 | from functools import wraps
 7 | 
 8 | from manimlib.utils.directories import get_cache_dir
 9 | from manimlib.utils.simple_functions import hash_string
10 | 
11 | from typing import TYPE_CHECKING
12 | 
13 | if TYPE_CHECKING:
14 |     T = TypeVar('T')
15 | 
16 | 
17 | CACHE_SIZE = 1e9  # 1 Gig
18 | _cache = Cache(get_cache_dir(), size_limit=CACHE_SIZE)
19 | 
20 | 
21 | def cache_on_disk(func: Callable[..., T]) -> Callable[..., T]:
22 |     @wraps(func)
23 |     def wrapper(*args, **kwargs):
24 |         key = hash_string(f"{func.__name__}{args}{kwargs}")
25 |         value = _cache.get(key)
26 |         if value is None:
27 |             value = func(*args, **kwargs)
28 |             _cache.set(key, value)
29 |         return value
30 |     return wrapper
31 | 
32 | 
33 | def clear_cache():
34 |     _cache.clear()
35 | 


--------------------------------------------------------------------------------
/manimlib/utils/color.py:
--------------------------------------------------------------------------------
  1 | from __future__ import annotations
  2 | 
  3 | from colour import Color
  4 | from colour import hex2rgb
  5 | from colour import rgb2hex
  6 | import numpy as np
  7 | import random
  8 | from matplotlib import pyplot
  9 | 
 10 | from manimlib.constants import COLORMAP_3B1B
 11 | from manimlib.constants import WHITE
 12 | from manimlib.utils.bezier import interpolate
 13 | from manimlib.utils.iterables import resize_with_interpolation
 14 | 
 15 | from typing import TYPE_CHECKING
 16 | 
 17 | if TYPE_CHECKING:
 18 |     from typing import Iterable, Sequence, Callable
 19 |     from manimlib.typing import ManimColor, Vect3, Vect4, Vect3Array, Vect4Array, NDArray
 20 | 
 21 | 
 22 | def color_to_rgb(color: ManimColor) -> Vect3:
 23 |     if isinstance(color, str):
 24 |         return hex_to_rgb(color)
 25 |     elif isinstance(color, Color):
 26 |         return np.array(color.get_rgb())
 27 |     else:
 28 |         raise Exception("Invalid color type")
 29 | 
 30 | 
 31 | def color_to_rgba(color: ManimColor, alpha: float = 1.0) -> Vect4:
 32 |     return np.array([*color_to_rgb(color), alpha])
 33 | 
 34 | 
 35 | def rgb_to_color(rgb: Vect3 | Sequence[float]) -> Color:
 36 |     try:
 37 |         return Color(rgb=tuple(rgb))
 38 |     except ValueError:
 39 |         return Color(WHITE)
 40 | 
 41 | 
 42 | def rgba_to_color(rgba: Vect4) -> Color:
 43 |     return rgb_to_color(rgba[:3])
 44 | 
 45 | 
 46 | def rgb_to_hex(rgb: Vect3 | Sequence[float]) -> str:
 47 |     return rgb2hex(rgb, force_long=True).upper()
 48 | 
 49 | 
 50 | def hex_to_rgb(hex_code: str) -> Vect3:
 51 |     return np.array(hex2rgb(hex_code))
 52 | 
 53 | 
 54 | def invert_color(color: ManimColor) -> Color:
 55 |     return rgb_to_color(1.0 - color_to_rgb(color))
 56 | 
 57 | 
 58 | def color_to_int_rgb(color: ManimColor) -> np.ndarray[int, np.dtype[np.uint8]]:
 59 |     return (255 * color_to_rgb(color)).astype('uint8')
 60 | 
 61 | 
 62 | def color_to_int_rgba(color: ManimColor, opacity: float = 1.0) -> np.ndarray[int, np.dtype[np.uint8]]:
 63 |     alpha = int(255 * opacity)
 64 |     return np.array([*color_to_int_rgb(color), alpha], dtype=np.uint8)
 65 | 
 66 | 
 67 | def color_to_hex(color: ManimColor) -> str:
 68 |     return Color(color).get_hex_l().upper()
 69 | 
 70 | 
 71 | def hex_to_int(rgb_hex: str) -> int:
 72 |     return int(rgb_hex[1:], 16)
 73 | 
 74 | 
 75 | def int_to_hex(rgb_int: int) -> str:
 76 |     return f"#{rgb_int:06x}".upper()
 77 | 
 78 | 
 79 | def color_gradient(
 80 |     reference_colors: Iterable[ManimColor],
 81 |     length_of_output: int
 82 | ) -> list[Color]:
 83 |     if length_of_output == 0:
 84 |         return []
 85 |     rgbs = list(map(color_to_rgb, reference_colors))
 86 |     alphas = np.linspace(0, (len(rgbs) - 1), length_of_output)
 87 |     floors = alphas.astype('int')
 88 |     alphas_mod1 = alphas % 1
 89 |     # End edge case
 90 |     alphas_mod1[-1] = 1
 91 |     floors[-1] = len(rgbs) - 2
 92 |     return [
 93 |         rgb_to_color(np.sqrt(interpolate(rgbs[i]**2, rgbs[i + 1]**2, alpha)))
 94 |         for i, alpha in zip(floors, alphas_mod1)
 95 |     ]
 96 | 
 97 | 
 98 | def interpolate_color(
 99 |     color1: ManimColor,
100 |     color2: ManimColor,
101 |     alpha: float
102 | ) -> Color:
103 |     rgb = np.sqrt(interpolate(color_to_rgb(color1)**2, color_to_rgb(color2)**2, alpha))
104 |     return rgb_to_color(rgb)
105 | 
106 | 
107 | def interpolate_color_by_hsl(
108 |     color1: ManimColor,
109 |     color2: ManimColor,
110 |     alpha: float
111 | ) -> Color:
112 |     hsl1 = np.array(Color(color1).get_hsl())
113 |     hsl2 = np.array(Color(color2).get_hsl())
114 |     return Color(hsl=interpolate(hsl1, hsl2, alpha))
115 | 
116 | 
117 | def average_color(*colors: ManimColor) -> Color:
118 |     rgbs = np.array(list(map(color_to_rgb, colors)))
119 |     return rgb_to_color(np.sqrt((rgbs**2).mean(0)))
120 | 
121 | 
122 | def random_color() -> Color:
123 |     return Color(rgb=tuple(np.random.random(3)))
124 | 
125 | 
126 | def random_bright_color(
127 |     hue_range: tuple[float, float] = (0.0, 1.0),
128 |     saturation_range: tuple[float, float] = (0.5, 0.8),
129 |     luminance_range: tuple[float, float] = (0.5, 1.0),
130 | ) -> Color:
131 |     return Color(hsl=(
132 |         interpolate(*hue_range, random.random()),
133 |         interpolate(*saturation_range, random.random()),
134 |         interpolate(*luminance_range, random.random()),
135 |     ))
136 | 
137 | 
138 | def get_colormap_from_colors(colors: Iterable[ManimColor]) -> Callable[[Sequence[float]], Vect4Array]:
139 |     """
140 |     Returns a funciton which takes in values between 0 and 1, and returns
141 |     a corresponding list of rgba values
142 |     """
143 |     rgbas = np.array([color_to_rgba(color) for color in colors])
144 | 
145 |     def func(values):
146 |         alphas = np.clip(values, 0, 1)
147 |         scaled_alphas = alphas * (len(rgbas) - 1)
148 |         indices = scaled_alphas.astype(int)
149 |         next_indices = np.clip(indices + 1, 0, len(rgbas) - 1)
150 |         inter_alphas = scaled_alphas % 1
151 |         inter_alphas = inter_alphas.repeat(4).reshape((len(indices), 4))
152 |         result = interpolate(rgbas[indices], rgbas[next_indices], inter_alphas)
153 |         return result
154 | 
155 |     return func
156 | 
157 | 
158 | def get_color_map(map_name: str) -> Callable[[Sequence[float]], Vect4Array]:
159 |     if map_name == "3b1b_colormap":
160 |         return get_colormap_from_colors(COLORMAP_3B1B)
161 |     return pyplot.get_cmap(map_name)
162 | 
163 | 
164 | # Delete this?
165 | def get_colormap_list(
166 |     map_name: str = "viridis",
167 |     n_colors: int = 9
168 | ) -> Vect3Array:
169 |     """
170 |     Options for map_name:
171 |     3b1b_colormap
172 |     magma
173 |     inferno
174 |     plasma
175 |     viridis
176 |     cividis
177 |     twilight
178 |     twilight_shifted
179 |     turbo
180 |     """
181 |     from matplotlib.cm import cmaps_listed
182 | 
183 |     if map_name == "3b1b_colormap":
184 |         rgbs = np.array([color_to_rgb(color) for color in COLORMAP_3B1B])
185 |     else:
186 |         rgbs = cmaps_listed[map_name].colors  # Make more general?
187 |     return resize_with_interpolation(np.array(rgbs), n_colors)
188 | 


--------------------------------------------------------------------------------
/manimlib/utils/debug.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from manimlib.constants import BLACK
 4 | from manimlib.logger import log
 5 | from manimlib.mobject.numbers import Integer
 6 | from manimlib.mobject.types.vectorized_mobject import VGroup
 7 | 
 8 | from typing import TYPE_CHECKING
 9 | 
10 | if TYPE_CHECKING:
11 |     from manimlib.mobject.mobject import Mobject
12 | 
13 | 
14 | def print_family(mobject: Mobject, n_tabs: int = 0) -> None:
15 |     """For debugging purposes"""
16 |     log.debug("\t" * n_tabs + str(mobject) + " " + str(id(mobject)))
17 |     for submob in mobject.submobjects:
18 |         print_family(submob, n_tabs + 1)
19 | 
20 | 
21 | def index_labels(
22 |     mobject: Mobject, 
23 |     label_height: float = 0.15
24 | ) -> VGroup:
25 |     labels = VGroup()
26 |     for n, submob in enumerate(mobject):
27 |         label = Integer(n)
28 |         label.set_height(label_height)
29 |         label.move_to(submob)
30 |         label.set_backstroke(BLACK, 5)
31 |         labels.add(label)
32 |     return labels
33 | 


--------------------------------------------------------------------------------
/manimlib/utils/dict_ops.py:
--------------------------------------------------------------------------------
 1 | import itertools as it
 2 | import numpy as np
 3 | 
 4 | 
 5 | def merge_dicts_recursively(*dicts):
 6 |     """
 7 |     Creates a dict whose keyset is the union of all the
 8 |     input dictionaries.  The value for each key is based
 9 |     on the first dict in the list with that key.
10 | 
11 |     dicts later in the list have higher priority
12 | 
13 |     When values are dictionaries, it is applied recursively
14 |     """
15 |     result = dict()
16 |     all_items = it.chain(*[d.items() for d in dicts])
17 |     for key, value in all_items:
18 |         if key in result and isinstance(result[key], dict) and isinstance(value, dict):
19 |             result[key] = merge_dicts_recursively(result[key], value)
20 |         else:
21 |             result[key] = value
22 |     return result
23 | 


--------------------------------------------------------------------------------
/manimlib/utils/directories.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import os
 4 | import tempfile
 5 | import appdirs
 6 | 
 7 | 
 8 | from manimlib.config import manim_config
 9 | from manimlib.config import get_manim_dir
10 | from manimlib.utils.file_ops import guarantee_existence
11 | 
12 | 
13 | def get_directories() -> dict[str, str]:
14 |     return manim_config.directories
15 | 
16 | 
17 | def get_cache_dir() -> str:
18 |     return get_directories()["cache"] or appdirs.user_cache_dir("manim")
19 | 
20 | 
21 | def get_temp_dir() -> str:
22 |     return get_directories()["temporary_storage"] or tempfile.gettempdir()
23 | 
24 | 
25 | def get_downloads_dir() -> str:
26 |     return get_directories()["downloads"] or appdirs.user_cache_dir("manim_downloads")
27 | 
28 | 
29 | def get_output_dir() -> str:
30 |     return guarantee_existence(get_directories()["output"])
31 | 
32 | 
33 | def get_raster_image_dir() -> str:
34 |     return get_directories()["raster_images"]
35 | 
36 | 
37 | def get_vector_image_dir() -> str:
38 |     return get_directories()["vector_images"]
39 | 
40 | 
41 | def get_sound_dir() -> str:
42 |     return get_directories()["sounds"]
43 | 
44 | 
45 | def get_shader_dir() -> str:
46 |     return os.path.join(get_manim_dir(), "manimlib", "shaders")
47 | 


--------------------------------------------------------------------------------
/manimlib/utils/family_ops.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from typing import TYPE_CHECKING
 4 | 
 5 | if TYPE_CHECKING:
 6 |     from typing import Iterable, List, Set, Tuple
 7 | 
 8 |     from manimlib.mobject.mobject import Mobject
 9 | 
10 | 
11 | def extract_mobject_family_members(
12 |     mobject_list: Iterable[Mobject],
13 |     exclude_pointless: bool = False
14 | ) -> list[Mobject]:
15 |     return [
16 |         sm
17 |         for mob in mobject_list
18 |         for sm in mob.get_family()
19 |         if (not exclude_pointless) or sm.has_points()
20 |     ]
21 | 
22 | 
23 | def recursive_mobject_remove(mobjects: List[Mobject], to_remove: Set[Mobject]) -> Tuple[List[Mobject], bool]:
24 |     """
25 |     Takes in a list of mobjects, together with a set of mobjects to remove.
26 | 
27 |     The first component of what's removed is a new list such that any mobject
28 |     with one of the elements from `to_remove` in its family is no longer in
29 |     the list, and in its place are its family members which aren't in `to_remove`
30 | 
31 |     The second component is a boolean value indicating whether any removals were made
32 |     """
33 |     result = []
34 |     found_in_list = False
35 |     for mob in mobjects:
36 |         if mob in to_remove:
37 |             found_in_list = True
38 |             continue
39 |         # Recursive call
40 |         sub_list, found_in_submobjects = recursive_mobject_remove(
41 |             mob.submobjects, to_remove
42 |         )
43 |         if found_in_submobjects:
44 |             result.extend(sub_list)
45 |             found_in_list = True
46 |         else:
47 |             result.append(mob)
48 |     return result, found_in_list


--------------------------------------------------------------------------------
/manimlib/utils/file_ops.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import os
 4 | from pathlib import Path
 5 | import hashlib
 6 | 
 7 | import numpy as np
 8 | import validators
 9 | import urllib.request
10 | 
11 | import manimlib.utils.directories
12 | from manimlib.utils.simple_functions import hash_string
13 | 
14 | from typing import TYPE_CHECKING
15 | 
16 | if TYPE_CHECKING:
17 |     from typing import Iterable
18 | 
19 | 
20 | def guarantee_existence(path: str | Path) -> Path:
21 |     path = Path(path)
22 |     path.mkdir(parents=True, exist_ok=True)
23 |     return path.absolute()
24 | 
25 | 
26 | def find_file(
27 |     file_name: str,
28 |     directories: Iterable[str] | None = None,
29 |     extensions: Iterable[str] | None = None
30 | ) -> Path:
31 |     # Check if this is a file online first, and if so, download
32 |     # it to a temporary directory
33 |     if validators.url(file_name):
34 |         suffix = Path(file_name).suffix
35 |         file_hash = hash_string(file_name)
36 |         folder = manimlib.utils.directories.get_downloads_dir()
37 | 
38 |         path = Path(folder, file_hash).with_suffix(suffix)
39 |         urllib.request.urlretrieve(file_name, path)
40 |         return path
41 | 
42 |     # Check if what was passed in is already a valid path to a file
43 |     if os.path.exists(file_name):
44 |         return Path(file_name)
45 | 
46 |     # Otherwise look in local file system
47 |     directories = directories or [""]
48 |     extensions = extensions or [""]
49 |     possible_paths = (
50 |         Path(directory, file_name + extension)
51 |         for directory in directories
52 |         for extension in extensions
53 |     )
54 |     for path in possible_paths:
55 |         if path.exists():
56 |             return path
57 |     raise IOError(f"{file_name} not Found")
58 | 


--------------------------------------------------------------------------------
/manimlib/utils/images.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import numpy as np
 4 | from PIL import Image
 5 | 
 6 | from manimlib.utils.directories import get_raster_image_dir
 7 | from manimlib.utils.directories import get_vector_image_dir
 8 | from manimlib.utils.file_ops import find_file
 9 | 
10 | from typing import TYPE_CHECKING
11 | 
12 | if TYPE_CHECKING:
13 |     from typing import Iterable
14 | 
15 | 
16 | def get_full_raster_image_path(image_file_name: str) -> str:
17 |     return find_file(
18 |         image_file_name,
19 |         directories=[get_raster_image_dir()],
20 |         extensions=[".jpg", ".jpeg", ".png", ".gif", ""]
21 |     )
22 | 
23 | 
24 | def get_full_vector_image_path(image_file_name: str) -> str:
25 |     return find_file(
26 |         image_file_name,
27 |         directories=[get_vector_image_dir()],
28 |         extensions=[".svg", ".xdv", ""],
29 |     )
30 | 
31 | 
32 | def invert_image(image: Iterable) -> Image.Image:
33 |     arr = np.array(image)
34 |     arr = (255 * np.ones(arr.shape)).astype(arr.dtype) - arr
35 |     return Image.fromarray(arr)
36 | 


--------------------------------------------------------------------------------
/manimlib/utils/iterables.py:
--------------------------------------------------------------------------------
  1 | from __future__ import annotations
  2 | 
  3 | from colour import Color
  4 | 
  5 | import numpy as np
  6 | import random
  7 | 
  8 | from typing import TYPE_CHECKING
  9 | 
 10 | if TYPE_CHECKING:
 11 |     from typing import Callable, Iterable, Sequence, TypeVar
 12 | 
 13 |     T = TypeVar("T")
 14 |     S = TypeVar("S")
 15 | 
 16 | 
 17 | def remove_list_redundancies(lst: Sequence[T]) -> list[T]:
 18 |     """
 19 |     Remove duplicate elements while preserving order.
 20 |     Keeps the last occurrence of each element
 21 |     """
 22 |     return list(reversed(dict.fromkeys(reversed(lst))))
 23 | 
 24 | 
 25 | def list_update(l1: Iterable[T], l2: Iterable[T]) -> list[T]:
 26 |     """
 27 |     Used instead of list(set(l1).update(l2)) to maintain order,
 28 |     making sure duplicates are removed from l1, not l2.
 29 |     """
 30 |     return remove_list_redundancies([*l1, *l2])
 31 | 
 32 | 
 33 | def list_difference_update(l1: Iterable[T], l2: Iterable[T]) -> list[T]:
 34 |     return [e for e in l1 if e not in l2]
 35 | 
 36 | 
 37 | def adjacent_n_tuples(objects: Sequence[T], n: int) -> zip[tuple[T, ...]]:
 38 |     return zip(*[
 39 |         [*objects[k:], *objects[:k]]
 40 |         for k in range(n)
 41 |     ])
 42 | 
 43 | 
 44 | def adjacent_pairs(objects: Sequence[T]) -> zip[tuple[T, T]]:
 45 |     return adjacent_n_tuples(objects, 2)
 46 | 
 47 | 
 48 | def batch_by_property(
 49 |     items: Iterable[T],
 50 |     property_func: Callable[[T], S]
 51 | ) -> list[tuple[T, S]]:
 52 |     """
 53 |     Takes in a list, and returns a list of tuples, (batch, prop)
 54 |     such that all items in a batch have the same output when
 55 |     put into property_func, and such that chaining all these
 56 |     batches together would give the original list (i.e. order is
 57 |     preserved)
 58 |     """
 59 |     batch_prop_pairs = []
 60 |     curr_batch = []
 61 |     curr_prop = None
 62 |     for item in items:
 63 |         prop = property_func(item)
 64 |         if prop != curr_prop:
 65 |             # Add current batch
 66 |             if len(curr_batch) > 0:
 67 |                 batch_prop_pairs.append((curr_batch, curr_prop))
 68 |             # Redefine curr
 69 |             curr_prop = prop
 70 |             curr_batch = [item]
 71 |         else:
 72 |             curr_batch.append(item)
 73 |     if len(curr_batch) > 0:
 74 |         batch_prop_pairs.append((curr_batch, curr_prop))
 75 |     return batch_prop_pairs
 76 | 
 77 | 
 78 | def listify(obj: object) -> list:
 79 |     if isinstance(obj, str):
 80 |         return [obj]
 81 |     try:
 82 |         return list(obj)
 83 |     except TypeError:
 84 |         return [obj]
 85 | 
 86 | 
 87 | def shuffled(iterable: Iterable) -> list:
 88 |     as_list = list(iterable)
 89 |     random.shuffle(as_list)
 90 |     return as_list
 91 | 
 92 | 
 93 | def resize_array(nparray: np.ndarray, length: int) -> np.ndarray:
 94 |     if len(nparray) == length:
 95 |         return nparray
 96 |     return np.resize(nparray, (length, *nparray.shape[1:]))
 97 | 
 98 | 
 99 | def resize_preserving_order(nparray: np.ndarray, length: int) -> np.ndarray:
100 |     if len(nparray) == 0:
101 |         return np.resize(nparray, length)
102 |     if len(nparray) == length:
103 |         return nparray
104 |     indices = np.arange(length) * len(nparray) // length
105 |     return nparray[indices]
106 | 
107 | 
108 | def resize_with_interpolation(nparray: np.ndarray, length: int) -> np.ndarray:
109 |     if len(nparray) == length:
110 |         return nparray
111 |     if len(nparray) == 1 or array_is_constant(nparray):
112 |         return nparray[:1].repeat(length, axis=0)
113 |     if length == 0:
114 |         return np.zeros((0, *nparray.shape[1:]))
115 |     cont_indices = np.linspace(0, len(nparray) - 1, length)
116 |     return np.array([
117 |         (1 - a) * nparray[lh] + a * nparray[rh]
118 |         for ci in cont_indices
119 |         for lh, rh, a in [(int(ci), int(np.ceil(ci)), ci % 1)]
120 |     ])
121 | 
122 | 
123 | def make_even(
124 |     iterable_1: Sequence[T],
125 |     iterable_2: Sequence[S]
126 | ) -> tuple[Sequence[T], Sequence[S]]:
127 |     len1 = len(iterable_1)
128 |     len2 = len(iterable_2)
129 |     if len1 == len2:
130 |         return iterable_1, iterable_2
131 |     new_len = max(len1, len2)
132 |     return (
133 |         [iterable_1[(n * len1) // new_len] for n in range(new_len)],
134 |         [iterable_2[(n * len2) // new_len] for n in range(new_len)]
135 |     )
136 | 
137 | 
138 | def arrays_match(arr1: np.ndarray, arr2: np.ndarray) -> bool:
139 |     return arr1.shape == arr2.shape and (arr1 == arr2).all()
140 | 
141 | 
142 | def array_is_constant(arr: np.ndarray) -> bool:
143 |     return len(arr) > 0 and (arr == arr[0]).all()
144 | 
145 | 
146 | def cartesian_product(*arrays: np.ndarray):
147 |     """
148 |     Copied from https://stackoverflow.com/a/11146645
149 |     """
150 |     la = len(arrays)
151 |     dtype = np.result_type(*arrays)
152 |     arr = np.empty([len(a) for a in arrays] + [la], dtype=dtype)
153 |     for i, a in enumerate(np.ix_(*arrays)):
154 |         arr[..., i] = a
155 |     return arr.reshape(-1, la)
156 | 
157 | 
158 | def hash_obj(obj: object) -> int:
159 |     if isinstance(obj, dict):
160 |         return hash(tuple(sorted([
161 |             (hash_obj(k), hash_obj(v)) for k, v in obj.items()
162 |         ])))
163 | 
164 |     if isinstance(obj, set):
165 |         return hash(tuple(sorted(hash_obj(e) for e in obj)))
166 | 
167 |     if isinstance(obj, (tuple, list)):
168 |         return hash(tuple(hash_obj(e) for e in obj))
169 | 
170 |     if isinstance(obj, Color):
171 |         return hash(obj.get_rgb())
172 | 
173 |     return hash(obj)
174 | 


--------------------------------------------------------------------------------
/manimlib/utils/paths.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import math
 4 | 
 5 | import numpy as np
 6 | 
 7 | from manimlib.constants import OUT
 8 | from manimlib.utils.bezier import interpolate
 9 | from manimlib.utils.space_ops import get_norm
10 | from manimlib.utils.space_ops import rotation_matrix_transpose
11 | 
12 | from typing import TYPE_CHECKING
13 | 
14 | if TYPE_CHECKING:
15 |     from typing import Callable
16 |     from manimlib.typing import Vect3, Vect3Array
17 | 
18 | 
19 | STRAIGHT_PATH_THRESHOLD = 0.01
20 | 
21 | 
22 | def straight_path(
23 |     start_points: np.ndarray,
24 |     end_points: np.ndarray,
25 |     alpha: float
26 | ) -> np.ndarray:
27 |     """
28 |     Same function as interpolate, but renamed to reflect
29 |     intent of being used to determine how a set of points move
30 |     to another set.  For instance, it should be a specific case
31 |     of path_along_arc
32 |     """
33 |     return interpolate(start_points, end_points, alpha)
34 | 
35 | 
36 | def path_along_arc(
37 |     arc_angle: float, 
38 |     axis: Vect3 = OUT
39 | ) -> Callable[[Vect3Array, Vect3Array, float], Vect3Array]:
40 |     """
41 |     If vect is vector from start to end, [vect[:,1], -vect[:,0]] is
42 |     perpendicular to vect in the left direction.
43 |     """
44 |     if abs(arc_angle) < STRAIGHT_PATH_THRESHOLD:
45 |         return straight_path
46 |     if get_norm(axis) == 0:
47 |         axis = OUT
48 |     unit_axis = axis / get_norm(axis)
49 | 
50 |     def path(start_points, end_points, alpha):
51 |         vects = end_points - start_points
52 |         centers = start_points + 0.5 * vects
53 |         if arc_angle != np.pi:
54 |             centers += np.cross(unit_axis, vects / 2.0) / math.tan(arc_angle / 2)
55 |         rot_matrix_T = rotation_matrix_transpose(alpha * arc_angle, unit_axis)
56 |         return centers + np.dot(start_points - centers, rot_matrix_T)
57 | 
58 |     return path
59 | 
60 | 
61 | def clockwise_path() -> Callable[[Vect3Array, Vect3Array, float], Vect3Array]:
62 |     return path_along_arc(-np.pi)
63 | 
64 | 
65 | def counterclockwise_path() -> Callable[[Vect3Array, Vect3Array, float], Vect3Array]:
66 |     return path_along_arc(np.pi)
67 | 


--------------------------------------------------------------------------------
/manimlib/utils/rate_functions.py:
--------------------------------------------------------------------------------
  1 | from __future__ import annotations
  2 | 
  3 | import numpy as np
  4 | 
  5 | from manimlib.utils.bezier import bezier
  6 | 
  7 | from typing import TYPE_CHECKING
  8 | 
  9 | if TYPE_CHECKING:
 10 |     from typing import Callable
 11 | 
 12 | 
 13 | def linear(t: float) -> float:
 14 |     return t
 15 | 
 16 | 
 17 | def smooth(t: float) -> float:
 18 |     # Zero first and second derivatives at t=0 and t=1.
 19 |     # Equivalent to bezier([0, 0, 0, 1, 1, 1])
 20 |     s = 1 - t
 21 |     return (t**3) * (10 * s * s + 5 * s * t + t * t)
 22 | 
 23 | 
 24 | def rush_into(t: float) -> float:
 25 |     return 2 * smooth(0.5 * t)
 26 | 
 27 | 
 28 | def rush_from(t: float) -> float:
 29 |     return 2 * smooth(0.5 * (t + 1)) - 1
 30 | 
 31 | 
 32 | def slow_into(t: float) -> float:
 33 |     return np.sqrt(1 - (1 - t) * (1 - t))
 34 | 
 35 | 
 36 | def double_smooth(t: float) -> float:
 37 |     if t < 0.5:
 38 |         return 0.5 * smooth(2 * t)
 39 |     else:
 40 |         return 0.5 * (1 + smooth(2 * t - 1))
 41 | 
 42 | 
 43 | def there_and_back(t: float) -> float:
 44 |     new_t = 2 * t if t < 0.5 else 2 * (1 - t)
 45 |     return smooth(new_t)
 46 | 
 47 | 
 48 | def there_and_back_with_pause(t: float, pause_ratio: float = 1. / 3) -> float:
 49 |     a = 2. / (1. - pause_ratio)
 50 |     if t < 0.5 - pause_ratio / 2:
 51 |         return smooth(a * t)
 52 |     elif t < 0.5 + pause_ratio / 2:
 53 |         return 1
 54 |     else:
 55 |         return smooth(a - a * t)
 56 | 
 57 | 
 58 | def running_start(t: float, pull_factor: float = -0.5) -> float:
 59 |     return bezier([0, 0, pull_factor, pull_factor, 1, 1, 1])(t)
 60 | 
 61 | 
 62 | def overshoot(t: float, pull_factor: float = 1.5) -> float:
 63 |     return bezier([0, 0, pull_factor, pull_factor, 1, 1])(t)
 64 | 
 65 | 
 66 | def not_quite_there(
 67 |     func: Callable[[float], float] = smooth,
 68 |     proportion: float = 0.7
 69 | ) -> Callable[[float], float]:
 70 |     def result(t):
 71 |         return proportion * func(t)
 72 |     return result
 73 | 
 74 | 
 75 | def wiggle(t: float, wiggles: float = 2) -> float:
 76 |     return there_and_back(t) * np.sin(wiggles * np.pi * t)
 77 | 
 78 | 
 79 | def squish_rate_func(
 80 |     func: Callable[[float], float],
 81 |     a: float = 0.4,
 82 |     b: float = 0.6
 83 | ) -> Callable[[float], float]:
 84 |     def result(t):
 85 |         if a == b:
 86 |             return a
 87 |         elif t < a:
 88 |             return func(0)
 89 |         elif t > b:
 90 |             return func(1)
 91 |         else:
 92 |             return func((t - a) / (b - a))
 93 | 
 94 |     return result
 95 | 
 96 | # Stylistically, should this take parameters (with default values)?
 97 | # Ultimately, the functionality is entirely subsumed by squish_rate_func,
 98 | # but it may be useful to have a nice name for with nice default params for
 99 | # "lingering", different from squish_rate_func's default params
100 | 
101 | 
102 | def lingering(t: float) -> float:
103 |     return squish_rate_func(lambda t: t, 0, 0.8)(t)
104 | 
105 | 
106 | def exponential_decay(t: float, half_life: float = 0.1) -> float:
107 |     # The half-life should be rather small to minimize
108 |     # the cut-off error at the end
109 |     return 1 - np.exp(-t / half_life)
110 | 


--------------------------------------------------------------------------------
/manimlib/utils/shaders.py:
--------------------------------------------------------------------------------
  1 | from __future__ import annotations
  2 | 
  3 | import os
  4 | import re
  5 | from functools import lru_cache
  6 | import moderngl
  7 | from PIL import Image
  8 | import numpy as np
  9 | 
 10 | from manimlib.utils.directories import get_shader_dir
 11 | from manimlib.utils.file_ops import find_file
 12 | 
 13 | from typing import TYPE_CHECKING
 14 | 
 15 | if TYPE_CHECKING:
 16 |     from typing import Sequence, Optional
 17 | 
 18 | 
 19 | # Global maps to reflect uniform status
 20 | PROGRAM_UNIFORM_MIRRORS: dict[int, dict[str, float | tuple]] = dict()
 21 | 
 22 | 
 23 | @lru_cache()
 24 | def image_path_to_texture(path: str, ctx: moderngl.Context) -> moderngl.Texture:
 25 |     im = Image.open(path).convert("RGBA")
 26 |     return ctx.texture(
 27 |         size=im.size,
 28 |         components=len(im.getbands()),
 29 |         data=im.tobytes(),
 30 |     )
 31 | 
 32 | 
 33 | @lru_cache()
 34 | def get_shader_program(
 35 |         ctx: moderngl.context.Context,
 36 |         vertex_shader: str,
 37 |         fragment_shader: Optional[str] = None,
 38 |         geometry_shader: Optional[str] = None,
 39 | ) -> moderngl.Program:
 40 |     return ctx.program(
 41 |         vertex_shader=vertex_shader,
 42 |         fragment_shader=fragment_shader,
 43 |         geometry_shader=geometry_shader,
 44 |     )
 45 | 
 46 | 
 47 | def set_program_uniform(
 48 |     program: moderngl.Program,
 49 |     name: str,
 50 |     value: float | tuple | np.ndarray
 51 | ) -> bool:
 52 |     """
 53 |     Sets a program uniform, and also keeps track of a dictionary
 54 |     of previously set uniforms for that program so that it
 55 |     doesn't needlessly reset it, requiring an exchange with gpu
 56 |     memory, if it sees the same value again.
 57 | 
 58 |     Returns True if changed the program, False if it left it as is.
 59 |     """
 60 | 
 61 |     pid = id(program)
 62 |     if pid not in PROGRAM_UNIFORM_MIRRORS:
 63 |         PROGRAM_UNIFORM_MIRRORS[pid] = dict()
 64 |     uniform_mirror = PROGRAM_UNIFORM_MIRRORS[pid]
 65 | 
 66 |     if type(value) is np.ndarray and value.ndim > 0:
 67 |         value = tuple(value.flatten())
 68 |     if uniform_mirror.get(name, None) == value:
 69 |         return False
 70 | 
 71 |     try:
 72 |         program[name].value = value
 73 |     except KeyError:
 74 |         return False
 75 |     uniform_mirror[name] = value
 76 |     return True
 77 | 
 78 | 
 79 | @lru_cache()
 80 | def get_shader_code_from_file(filename: str) -> str | None:
 81 |     if not filename:
 82 |         return None
 83 | 
 84 |     try:
 85 |         filepath = find_file(
 86 |             filename,
 87 |             directories=[get_shader_dir(), "/"],
 88 |             extensions=[],
 89 |         )
 90 |     except IOError:
 91 |         return None
 92 | 
 93 |     with open(filepath, "r") as f:
 94 |         result = f.read()
 95 | 
 96 |     # To share functionality between shaders, some functions are read in
 97 |     # from other files an inserted into the relevant strings before
 98 |     # passing to ctx.program for compiling
 99 |     # Replace "#INSERT " lines with relevant code
100 |     insertions = re.findall(r"^#INSERT .*\.glsl
quot;, result, flags=re.MULTILINE)
101 |     for line in insertions:
102 |         inserted_code = get_shader_code_from_file(
103 |             os.path.join("inserts", line.replace("#INSERT ", ""))
104 |         )
105 |         result = result.replace(line, inserted_code)
106 |     return result
107 | 
108 | 
109 | def get_colormap_code(rgb_list: Sequence[float]) -> str:
110 |     data = ",".join(
111 |         "vec3({}, {}, {})".format(*rgb)
112 |         for rgb in rgb_list
113 |     )
114 |     return f"vec3[{len(rgb_list)}]({data})"
115 | 


--------------------------------------------------------------------------------
/manimlib/utils/simple_functions.py:
--------------------------------------------------------------------------------
  1 | from __future__ import annotations
  2 | 
  3 | from functools import lru_cache
  4 | import hashlib
  5 | import inspect
  6 | import math
  7 | 
  8 | import numpy as np
  9 | 
 10 | from typing import TYPE_CHECKING
 11 | if TYPE_CHECKING:
 12 |     from typing import Callable, TypeVar, Iterable
 13 |     from manimlib.typing import FloatArray
 14 | 
 15 |     Scalable = TypeVar("Scalable", float, FloatArray)
 16 | 
 17 | 
 18 | 
 19 | def sigmoid(x: float | FloatArray):
 20 |     return 1.0 / (1 + np.exp(-x))
 21 | 
 22 | 
 23 | @lru_cache(maxsize=10)
 24 | def choose(n: int, k: int) -> int:
 25 |     return math.comb(n, k)
 26 | 
 27 | 
 28 | def gen_choose(n: int, r: int) -> int:
 29 |     return int(np.prod(range(n, n - r, -1)) / math.factorial(r))
 30 | 
 31 | 
 32 | def get_num_args(function: Callable) -> int:
 33 |     return function.__code__.co_argcount
 34 | 
 35 | 
 36 | def get_parameters(function: Callable) -> Iterable[str]:
 37 |     return inspect.signature(function).parameters.keys()
 38 | 
 39 | 
 40 | def clip(a: float, min_a: float, max_a: float) -> float:
 41 |     if a < min_a:
 42 |         return min_a
 43 |     elif a > max_a:
 44 |         return max_a
 45 |     return a
 46 | 
 47 | 
 48 | def arr_clip(arr: np.ndarray, min_a: float, max_a: float) -> np.ndarray:
 49 |     arr[arr < min_a] = min_a
 50 |     arr[arr > max_a] = max_a
 51 |     return arr
 52 | 
 53 | 
 54 | def fdiv(a: Scalable, b: Scalable, zero_over_zero_value: Scalable | None = None) -> Scalable:
 55 |     """
 56 |     Less heavyweight name for np.true_divide, enabling
 57 |     default behavior for 0/0
 58 |     """
 59 |     if zero_over_zero_value is not None:
 60 |         out = np.full_like(a, zero_over_zero_value)
 61 |         where = np.logical_or(a != 0, b != 0)
 62 |     else:
 63 |         out = None
 64 |         where = True
 65 | 
 66 |     return np.true_divide(a, b, out=out, where=where)
 67 | 
 68 | 
 69 | def binary_search(
 70 |     function: Callable[[float], float],
 71 |     target: float,
 72 |     lower_bound: float,
 73 |     upper_bound: float,
 74 |     tolerance:float = 1e-4
 75 | ) -> float | None:
 76 |     lh = lower_bound
 77 |     rh = upper_bound
 78 |     mh = (lh + rh) / 2
 79 |     while abs(rh - lh) > tolerance:
 80 |         lx, mx, rx = [function(h) for h in (lh, mh, rh)]
 81 |         if lx == target:
 82 |             return lx
 83 |         if rx == target:
 84 |             return rx
 85 | 
 86 |         if lx <= target and rx >= target:
 87 |             if mx > target:
 88 |                 rh = mh
 89 |             else:
 90 |                 lh = mh
 91 |         elif lx > target and rx < target:
 92 |             lh, rh = rh, lh
 93 |         else:
 94 |             return None
 95 |         mh = (lh + rh) / 2
 96 |     return mh
 97 | 
 98 | 
 99 | def hash_string(string: str, n_bytes=16) -> str:
100 |     hasher = hashlib.sha256(string.encode())
101 |     return hasher.hexdigest()[:n_bytes]
102 | 


--------------------------------------------------------------------------------
/manimlib/utils/sounds.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import subprocess
 4 | import threading
 5 | import platform
 6 | 
 7 | from manimlib.utils.directories import get_sound_dir
 8 | from manimlib.utils.file_ops import find_file
 9 | 
10 | 
11 | def get_full_sound_file_path(sound_file_name: str) -> str:
12 |     return find_file(
13 |         sound_file_name,
14 |         directories=[get_sound_dir()],
15 |         extensions=[".wav", ".mp3", ""]
16 |     )
17 | 
18 | 
19 | def play_sound(sound_file):
20 |     """Play a sound file using the system's audio player"""
21 |     full_path = get_full_sound_file_path(sound_file)
22 |     system = platform.system()
23 | 
24 |     if system == "Windows":
25 |         # Windows
26 |         subprocess.Popen(
27 |             ["powershell", "-c", f"(New-Object Media.SoundPlayer '{full_path}').PlaySync()"],
28 |             shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
29 |     )
30 |     elif system == "Darwin":
31 |         # macOS
32 |         subprocess.Popen(
33 |             ["afplay", full_path],
34 |             stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
35 |         )
36 |     else:
37 |         subprocess.Popen(
38 |             ["aplay", full_path],
39 |             stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
40 |         )
41 | 


--------------------------------------------------------------------------------
/manimlib/utils/tex.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import re
 4 | from functools import lru_cache
 5 | 
 6 | from manimlib.utils.tex_to_symbol_count import TEX_TO_SYMBOL_COUNT
 7 | 
 8 | 
 9 | @lru_cache
10 | def num_tex_symbols(tex: str) -> int:
11 |     tex = remove_tex_environments(tex)
12 |     commands_pattern = r"""
13 |         (?P<sqrt>\\sqrt\[[0-9]+\])|    # Special sqrt with number
14 |         (?P<escaped_brace>\\[{}])|      # Escaped braces
15 |         (?P<cmd>\\[a-zA-Z!,-/:;<>]+)    # Regular commands
16 |     """
17 |     total = 0
18 |     pos = 0
19 |     for match in re.finditer(commands_pattern, tex, re.VERBOSE):
20 |         # Count normal characters up to this command
21 |         total += sum(1 for c in tex[pos:match.start()] if c not in "^{} \n\t_$\\&")
22 | 
23 |         if match.group("sqrt"):
24 |             total += len(match.group()) - 5
25 |         elif match.group("escaped_brace"):
26 |             total += 1  # Count escaped brace as one symbol
27 |         else:
28 |             total += TEX_TO_SYMBOL_COUNT.get(match.group(), 1)
29 |         pos = match.end()
30 | 
31 |     # Count remaining characters
32 |     total += sum(1 for c in tex[pos:] if c not in "^{} \n\t_$\\&")
33 |     return total
34 | 
35 | 
36 | def remove_tex_environments(tex: str) -> str:
37 |     # Handle \phantom{...} with any content
38 |     tex = re.sub(r"\\phantom\{[^}]*\}", "", tex)
39 |     # Handle other environment commands
40 |     tex = re.sub(r"\\(begin|end)(\{\w+\})?(\{\w+\})?(\[\w+\])?", "", tex)
41 |     return tex
42 | 


--------------------------------------------------------------------------------
/manimlib/utils/tex_file_writing.py:
--------------------------------------------------------------------------------
  1 | from __future__ import annotations
  2 | 
  3 | import os
  4 | import re
  5 | import yaml
  6 | import subprocess
  7 | from functools import lru_cache
  8 | 
  9 | from pathlib import Path
 10 | import tempfile
 11 | 
 12 | from manimlib.utils.cache import cache_on_disk
 13 | from manimlib.config import manim_config
 14 | from manimlib.config import get_manim_dir
 15 | from manimlib.logger import log
 16 | from manimlib.utils.simple_functions import hash_string
 17 | 
 18 | 
 19 | def get_tex_template_config(template_name: str) -> dict[str, str]:
 20 |     name = template_name.replace(" ", "_").lower()
 21 |     template_path = os.path.join(get_manim_dir(), "manimlib", "tex_templates.yml")
 22 |     with open(template_path, encoding="utf-8") as tex_templates_file:
 23 |         templates_dict = yaml.safe_load(tex_templates_file)
 24 |     if name not in templates_dict:
 25 |         log.warning(f"Cannot recognize template {name}, falling back to 'default'.")
 26 |         name = "default"
 27 |     return templates_dict[name]
 28 | 
 29 | 
 30 | @lru_cache
 31 | def get_tex_config(template: str = "") -> tuple[str, str]:
 32 |     """
 33 |     Returns a compiler and preamble to use for rendering LaTeX
 34 |     """
 35 |     template = template or manim_config.tex.template
 36 |     config = get_tex_template_config(template)
 37 |     return config["compiler"], config["preamble"]
 38 | 
 39 | 
 40 | def get_full_tex(content: str, preamble: str = ""):
 41 |     return "\n\n".join((
 42 |         "\\documentclass[preview]{standalone}",
 43 |         preamble,
 44 |         "\\begin{document}",
 45 |         content,
 46 |         "\\end{document}"
 47 |     )) + "\n"
 48 | 
 49 | 
 50 | @lru_cache(maxsize=128)
 51 | def latex_to_svg(
 52 |     latex: str,
 53 |     template: str = "",
 54 |     additional_preamble: str = "",
 55 |     short_tex: str = "",
 56 |     show_message_during_execution: bool = True,
 57 | ) -> str:
 58 |     """Convert LaTeX string to SVG string.
 59 | 
 60 |     Args:
 61 |         latex: LaTeX source code
 62 |         template: Path to a template LaTeX file
 63 |         additional_preamble: String including any added "\\usepackage{...}" style imports
 64 | 
 65 |     Returns:
 66 |         str: SVG source code
 67 | 
 68 |     Raises:
 69 |         LatexError: If LaTeX compilation fails
 70 |         NotImplementedError: If compiler is not supported
 71 |     """
 72 |     if show_message_during_execution:
 73 |         message = f"Writing {(short_tex or latex)[:70]}..."
 74 |     else:
 75 |         message = ""
 76 | 
 77 |     compiler, preamble = get_tex_config(template)
 78 | 
 79 |     preamble = "\n".join([preamble, additional_preamble])
 80 |     full_tex = get_full_tex(latex, preamble)
 81 |     return full_tex_to_svg(full_tex, compiler, message)
 82 | 
 83 | 
 84 | @cache_on_disk
 85 | def full_tex_to_svg(full_tex: str, compiler: str = "latex", message: str = ""):
 86 |     if message:
 87 |         print(message, end="\r")
 88 | 
 89 |     if compiler == "latex":
 90 |         dvi_ext = ".dvi"
 91 |     elif compiler == "xelatex":
 92 |         dvi_ext = ".xdv"
 93 |     else:
 94 |         raise NotImplementedError(f"Compiler '{compiler}' is not implemented")
 95 | 
 96 |     # Write intermediate files to a temporary directory
 97 |     with tempfile.TemporaryDirectory() as temp_dir:
 98 |         tex_path = Path(temp_dir, "working").with_suffix(".tex")
 99 |         dvi_path = tex_path.with_suffix(dvi_ext)
100 | 
101 |         # Write tex file
102 |         tex_path.write_text(full_tex)
103 | 
104 |         # Run latex compiler
105 |         process = subprocess.run(
106 |             [
107 |                 compiler,
108 |                 *(['-no-pdf'] if compiler == "xelatex" else []),
109 |                 "-interaction=batchmode",
110 |                 "-halt-on-error",
111 |                 f"-output-directory={temp_dir}",
112 |                 tex_path
113 |             ],
114 |             capture_output=True,
115 |             text=True
116 |         )
117 | 
118 |         if process.returncode != 0:
119 |             # Handle error
120 |             error_str = ""
121 |             log_path = tex_path.with_suffix(".log")
122 |             if log_path.exists():
123 |                 content = log_path.read_text()
124 |                 error_match = re.search(r"(?<=\n! ).*\n.*\n", content)
125 |                 if error_match:
126 |                     error_str = error_match.group()
127 |             raise LatexError(error_str or "LaTeX compilation failed")
128 | 
129 |         # Run dvisvgm and capture output directly
130 |         process = subprocess.run(
131 |             [
132 |                 "dvisvgm",
133 |                 dvi_path,
134 |                 "-n",  # no fonts
135 |                 "-v", "0",  # quiet
136 |                 "--stdout",  # output to stdout instead of file
137 |             ],
138 |             capture_output=True
139 |         )
140 | 
141 |         # Return SVG string
142 |         result = process.stdout.decode('utf-8')
143 | 
144 |     if message:
145 |         print(" " * len(message), end="\r")
146 | 
147 |     return result
148 | 
149 | 
150 | class LatexError(Exception):
151 |     pass
152 | 


--------------------------------------------------------------------------------
/manimlib/utils/tex_to_symbol_count.py:
--------------------------------------------------------------------------------
  1 | TEX_TO_SYMBOL_COUNT = {
  2 |     R"\!": 0,
  3 |     R"\,": 0,
  4 |     R"\-": 0,
  5 |     R"\/": 0,
  6 |     R"\:": 0,
  7 |     R"\;": 0,
  8 |     R"\>": 0,
  9 |     R"\aa": 0,
 10 |     R"\AA": 0,
 11 |     R"\ae": 0,
 12 |     R"\AE": 0,
 13 |     R"\arccos": 6,
 14 |     R"\arcsin": 6,
 15 |     R"\arctan": 6,
 16 |     R"\arg": 3,
 17 |     R"\author": 0,
 18 |     R"\bf": 0,
 19 |     R"\bibliography": 0,
 20 |     R"\bibliographystyle": 0,
 21 |     R"\big": 0,
 22 |     R"\Big": 0,
 23 |     R"\bigodot": 4,
 24 |     R"\bigoplus": 5,
 25 |     R"\bigskip": 0,
 26 |     R"\bmod": 3,
 27 |     R"\boldmath": 0,
 28 |     R"\bottomfraction": 2,
 29 |     R"\bowtie": 2,
 30 |     R"\cal": 0,
 31 |     R"\cdots": 3,
 32 |     R"\centering": 0,
 33 |     R"\cite": 2,
 34 |     R"\cong": 2,
 35 |     R"\contentsline": 0,
 36 |     R"\cos": 3,
 37 |     R"\cosh": 4,
 38 |     R"\cot": 3,
 39 |     R"\coth": 4,
 40 |     R"\csc": 3,
 41 |     R"\date": 0,
 42 |     R"\dblfloatpagefraction": 2,
 43 |     R"\dbltopfraction": 2,
 44 |     R"\ddots": 3,
 45 |     R"\deg": 3,
 46 |     R"\det": 3,
 47 |     R"\dim": 3,
 48 |     R"\displaystyle": 0,
 49 |     R"\div": 2,
 50 |     R"\doteq": 2,
 51 |     R"\dotfill": 0,
 52 |     R"\dots": 3,
 53 |     R"\emph": 0,
 54 |     R"\exp": 3,
 55 |     R"\fbox": 4,
 56 |     R"\floatpagefraction": 2,
 57 |     R"\flushbottom": 0,
 58 |     R"\footnotesize": 0,
 59 |     R"\footnotetext": 0,
 60 |     R"\frame": 2,
 61 |     R"\framebox": 4,
 62 |     R"\fussy": 0,
 63 |     R"\gcd": 3,
 64 |     R"\ghost": 0,
 65 |     R"\glossary": 0,
 66 |     R"\hfill": 0,
 67 |     R"\hom": 3,
 68 |     R"\hookleftarrow": 2,
 69 |     R"\hookrightarrow": 2,
 70 |     R"\hrulefill": 0,
 71 |     R"\huge": 0,
 72 |     R"\Huge": 0,
 73 |     R"\hyphenation": 0,
 74 |     R"\iff": 2,
 75 |     R"\Im": 2,
 76 |     R"\index": 0,
 77 |     R"\inf": 3,
 78 |     R"\it": 0,
 79 |     R"\ker": 3,
 80 |     R"\l": 0,
 81 |     R"\L": 0,
 82 |     R"\label": 0,
 83 |     R"\large": 0,
 84 |     R"\Large": 0,
 85 |     R"\LARGE": 0,
 86 |     R"\ldots": 3,
 87 |     R"\lefteqn": 0,
 88 |     R"\left": 0,
 89 |     R"\lg": 2,
 90 |     R"\lim": 3,
 91 |     R"\liminf": 6,
 92 |     R"\limsup": 6,
 93 |     R"\linebreak": 0,
 94 |     R"\ln": 2,
 95 |     R"\log": 3,
 96 |     R"\longleftarrow": 2,
 97 |     R"\Longleftarrow": 2,
 98 |     R"\longleftrightarrow": 2,
 99 |     R"\Longleftrightarrow": 2,
100 |     R"\longmapsto": 3,
101 |     R"\longrightarrow": 2,
102 |     R"\Longrightarrow": 2,
103 |     R"\makebox": 0,
104 |     R"\mapsto": 2,
105 |     R"\markright": 0,
106 |     R"\mathds": 0,
107 |     R"\max": 3,
108 |     R"\mbox": 0,
109 |     R"\medskip": 0,
110 |     R"\min": 3,
111 |     R"\mit": 0,
112 |     R"\models": 2,
113 |     R"\ne": 2,
114 |     R"\neq": 2,
115 |     R"\newline": 0,
116 |     R"\noindent": 0,
117 |     R"\nolinebreak": 0,
118 |     R"\nonumber": 0,
119 |     R"\nopagebreak": 0,
120 |     R"\normalmarginpar": 0,
121 |     R"\normalsize": 0,
122 |     R"\notin": 2,
123 |     R"\o": 0,
124 |     R"\O": 0,
125 |     R"\obeycr": 0,
126 |     R"\oe": 0,
127 |     R"\OE": 0,
128 |     R"\overbrace": 4,
129 |     R"\pagebreak": 0,
130 |     R"\pagenumbering": 0,
131 |     R"\pageref": 2,
132 |     R"\pmod": 5,
133 |     R"\Pr": 2,
134 |     R"\protect": 0,
135 |     R"\qquad": 0,
136 |     R"\quad": 0,
137 |     R"\raggedbottom": 0,
138 |     R"\raggedleft": 0,
139 |     R"\raggedright": 0,
140 |     R"\Re": 2,
141 |     R"\ref": 2,
142 |     R"\restorecr": 0,
143 |     R"\reversemarginpar": 0,
144 |     R"\right": 0,
145 |     R"\rm": 0,
146 |     R"\sc": 0,
147 |     R"\scriptscriptstyle": 0,
148 |     R"\scriptsize": 0,
149 |     R"\scriptstyle": 0,
150 |     R"\sec": 3,
151 |     R"\sf": 0,
152 |     R"\shortstack": 0,
153 |     R"\sin": 3,
154 |     R"\sinh": 4,
155 |     R"\sl": 0,
156 |     R"\sloppy": 0,
157 |     R"\small": 0,
158 |     R"\Small": 0,
159 |     R"\smallskip": 0,
160 |     R"\sqrt": 2,
161 |     R"\ss": 0,
162 |     R"\sup": 3,
163 |     R"\tan": 3,
164 |     R"\tanh": 4,
165 |     R"\text": 0,
166 |     R"\textbf": 0,
167 |     R"\textfraction": 2,
168 |     R"\textstyle": 0,
169 |     R"\thicklines": 0,
170 |     R"\thinlines": 0,
171 |     R"\thinspace": 0,
172 |     R"\tiny": 0,
173 |     R"\title": 0,
174 |     R"\today": 15,
175 |     R"\topfraction": 2,
176 |     R"\tt": 0,
177 |     R"\typeout": 0,
178 |     R"\unboldmath": 0,
179 |     R"\underbrace": 6,
180 |     R"\underline": 0,
181 |     R"\value": 0,
182 |     R"\vdots": 3,
183 |     R"\vline": 0
184 | }


--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools", "wheel"]


--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
 1 | addict
 2 | appdirs
 3 | audioop-lts; python_version>='3.13'
 4 | colour
 5 | diskcache
 6 | ipython>=8.18.0
 7 | isosurfaces
 8 | fontTools
 9 | manimpango>=0.6.0
10 | mapbox-earcut
11 | matplotlib
12 | moderngl
13 | moderngl_window
14 | numpy
15 | Pillow
16 | pydub
17 | pygments
18 | PyOpenGL
19 | pyperclip
20 | pyyaml
21 | rich
22 | scipy
23 | screeninfo
24 | setuptools
25 | skia-pathops
26 | svgelements>=1.8.1
27 | sympy
28 | tqdm
29 | typing-extensions; python_version < "3.11"
30 | validators
31 | 


--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
 1 | [metadata]
 2 | name = manimgl
 3 | version = 1.7.2
 4 | author = Grant Sanderson
 5 | author_email= grant@3blue1brown.com
 6 | description = Animation engine for explanatory math videos
 7 | long_description = file: README.md
 8 | long_description_content_type = text/markdown; charset=UTF-8
 9 | home_page = https://github.com/3b1b/manim
10 | project_urls =
11 |     Bug Tracker = https://github.com/3b1b/manim/issues
12 |     Documentation = https://3b1b.github.io/manim/
13 |     Source Code = https://github.com/3b1b/manim
14 | license = MIT
15 | classifiers =
16 |     Development Status :: 4 - Beta
17 |     License :: OSI Approved :: MIT License
18 |     Topic :: Scientific/Engineering
19 |     Topic :: Multimedia :: Video
20 |     Topic :: Multimedia :: Graphics
21 |     Programming Language :: Python :: 3.7
22 |     Programming Language :: Python :: 3.8
23 |     Programming Language :: Python :: 3.9
24 |     Programming Language :: Python :: 3.10
25 |     Programming Language :: Python :: 3 :: Only
26 |     Natural Language :: English
27 | 
28 | [options]
29 | packages = find:
30 | include_package_data = True
31 | install_requires =
32 |     addict
33 |     appdirs
34 |     audioop-lts; python_version >= "3.13"
35 |     colour
36 |     diskcache
37 |     ipython>=8.18.0
38 |     isosurfaces
39 |     fontTools
40 |     manimpango>=0.6.0
41 |     mapbox-earcut
42 |     matplotlib
43 |     moderngl
44 |     moderngl_window
45 |     numpy
46 |     Pillow
47 |     pydub
48 |     pygments
49 |     PyOpenGL
50 |     pyperclip
51 |     pyyaml
52 |     rich
53 |     scipy
54 |     screeninfo
55 |     setuptools
56 |     skia-pathops
57 |     svgelements>=1.8.1
58 |     sympy
59 |     tqdm
60 |     typing-extensions; python_version < "3.11"
61 |     validators
62 | 
63 | [options.entry_points]
64 | console_scripts =
65 |     manimgl = manimlib.__main__:main
66 |     manim-render = manimlib.__main__:main
67 | 


--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import setuptools
2 | setuptools.setup()


--------------------------------------------------------------------------------