[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n### Describe the bug\n<!-- A clear and concise description of what the bug is. -->\n\n**Code**:\n<!-- The code you run which reflect the bug. -->\n\n**Wrong display or Error traceback**:\n<!-- the wrong display result of the code you run, or the error Traceback -->\n\n### Additional context\n<!-- Add any other context about the problem here. -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: true\ncontact_links:\n  - name: Ask A Question\n    url: https://github.com/3b1b/manim/discussions/categories/q-a\n    about: Please ask questions you encountered here."
  },
  {
    "path": ".github/ISSUE_TEMPLATE/error-when-using.md",
    "content": "---\nname: Error when using\nabout: The error you encountered while using manim\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n### Describe the error\n<!-- A clear and concise description of what you want to make. -->\n\n### Code and Error\n**Code**:\n<!-- The code you run -->\n\n**Error**:\n<!-- The error traceback you get when run your code -->\n\n### Environment\n**OS System**: \n**manim version**: master <!-- make sure you are using the latest version of master branch -->\n**python version**:\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!-- Thanks for contributing to manim!\n    Please ensure that your pull request works with the latest version of manim.\n-->\n\n## Motivation\n<!-- Outline your motivation: In what way do your changes improve the library? -->\n\n## Proposed changes\n<!-- What you changed in those files -->\n- \n- \n- \n\n## Test\n<!-- How do you test your changes -->\n**Code**:\n\n**Result**:"
  },
  {
    "path": ".github/workflows/docs.yml",
    "content": "name: docs\n\non: \n  push:\n    paths:\n      - 'docs/**'\n  pull_request:\n    paths:\n      - 'docs/**'\n\njobs:\n  docs:\n    runs-on: ubuntu-latest\n    name: build up document and deploy\n\n    steps:\n    - name: Checkout\n      uses: actions/checkout@master\n    \n    - name: Install sphinx and manim env\n      run: |\n        pip3 install --upgrade pip\n        sudo apt install python3-setuptools libpango1.0-dev\n        pip3 install -r docs/requirements.txt\n        pip3 install -r requirements.txt\n    \n    - name: Build document with Sphinx\n      run: |\n        cd docs\n        export PATH=\"$PATH:/home/runner/.local/bin\"\n        export SPHINXBUILD=\"python3 -m sphinx\"\n        make html\n        \n    - name: Deploy to GitHub pages\n      if: ${{ github.event_name == 'push' }}\n      uses: JamesIves/github-pages-deploy-action@3.7.1\n      with:\n        ACCESS_TOKEN: ${{ secrets.DOC_DEPLOY_TOKEN }}\n        BRANCH: gh-pages\n        FOLDER: docs/build/html\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: Upload Python Package\n\non:\n  release:\n    types: [created]\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n\n    strategy:\n      fail-fast: false\n      matrix:\n        python: [\"py37\", \"py38\", \"py39\", \"py310\"]\n\n    steps:\n    - uses: actions/checkout@v6\n\n    - name: Set up Python\n      uses: actions/setup-python@v6\n      with:\n        python-version: '3.8'\n\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        pip install setuptools wheel twine build\n\n    - name: Build wheels\n      run: python setup.py bdist_wheel --python-tag ${{ matrix.python }}\n\n    - name: Upload wheels\n      env:\n        TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}\n        TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}\n      run: |\n        twine upload dist/*"
  },
  {
    "path": ".gitignore",
    "content": "# Created by https://www.toptal.com/developers/gitignore/api/python\n# Edit at https://www.toptal.com/developers/gitignore?templates=python\n\n### Python ###\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\nmanimlib.egg-info/\n\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\npip-wheel-metadata/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\npytestdebug.log\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\ndoc/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n.python-version\npyrightconfig.json \n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\npythonenv*\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# profiling data\n.prof\n\n# End of https://www.toptal.com/developers/gitignore/api/python\n# Custom exclusions:\n.DS_Store\n\n# For manim\n/videos\n/custom_config.yml\ntest.py\nCLAUDE.md\n"
  },
  {
    "path": "LICENSE.md",
    "content": "MIT License\n\nCopyright (c) 2020-2023 3Blue1Brown LLC\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "graft manimlib\nrecursive-exclude manimlib *.pyc *.DS_Store"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n    <a href=\"https://github.com/3b1b/manim\">\n        <img src=\"https://raw.githubusercontent.com/3b1b/manim/master/logo/cropped.png\">\n    </a>\n</p>\n\n[![pypi version](https://img.shields.io/pypi/v/manimgl?logo=pypi)](https://pypi.org/project/manimgl/)\n[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://choosealicense.com/licenses/mit/)\n[![Manim Subreddit](https://img.shields.io/reddit/subreddit-subscribers/manim.svg?color=ff4301&label=reddit&logo=reddit)](https://www.reddit.com/r/manim/)\n[![Manim Discord](https://img.shields.io/discord/581738731934056449.svg?label=discord&logo=discord)](https://discord.com/invite/bYCyhM9Kz2)\n[![docs](https://github.com/3b1b/manim/workflows/docs/badge.svg)](https://3b1b.github.io/manim/)\n\nManim is an engine for precise programmatic animations, designed for creating explanatory math videos.\n\nNote, there are two versions of manim.  This repository began as a personal project by the author of [3Blue1Brown](https://www.3blue1brown.com/) for the purpose of animating those videos, with video-specific code available [here](https://github.com/3b1b/videos).  In 2020 a group of developers forked it into what is now the [community edition](https://github.com/ManimCommunity/manim/), with a goal of being more stable, better tested, quicker to respond to community contributions, and all around friendlier to get started with. See [this page](https://docs.manim.community/en/stable/faq/installation.html#different-versions) for more details.\n\n## Installation\n> [!Warning]\n> **WARNING:** These instructions are for ManimGL _only_. Trying to use these instructions to install [Manim Community/manim](https://github.com/ManimCommunity/manim) or instructions there to install this version will cause problems. You should first decide which version you wish to install, then only follow the instructions for your desired version.\n\n> [!Note]\n> **Note**: To install manim directly through pip, please pay attention to the name of the installed package. This repository is ManimGL of 3b1b. The package name is `manimgl` instead of `manim` or `manimlib`. Please use `pip install manimgl` to install the version in this repository.\n\nManim runs on Python 3.7 or higher.\n\nSystem requirements are [FFmpeg](https://ffmpeg.org/), [OpenGL](https://www.opengl.org/) and [LaTeX](https://www.latex-project.org) (optional, if you want to use LaTeX).\nFor Linux, [Pango](https://pango.org) along with its development headers are required. See instruction [here](https://github.com/ManimCommunity/ManimPango#building).\n\n\n### Directly\n\n```sh\n# Install manimgl\npip install manimgl\n\n# Try it out\nmanimgl\n```\n\nFor more options, take a look at the [Using manim](#using-manim) sections further below.\n\nIf you want to hack on manimlib itself, clone this repository and in that directory execute:\n\n```sh\n# Install manimgl\npip install -e .\n\n# Try it out\nmanimgl example_scenes.py OpeningManimExample\n# or\nmanim-render example_scenes.py OpeningManimExample\n```\n\n### Directly (Windows)\n\n1. [Install FFmpeg](https://www.wikihow.com/Install-FFmpeg-on-Windows).\n2. Install a LaTeX distribution. [MiKTeX](https://miktex.org/download) is recommended.\n3. Install the remaining Python packages.\n    ```sh\n    git clone https://github.com/3b1b/manim.git\n    cd manim\n    pip install -e .\n    manimgl example_scenes.py OpeningManimExample\n    ```\n\n### Mac OSX\n\n1. Install FFmpeg, LaTeX in terminal using homebrew.\n    ```sh\n    brew install ffmpeg mactex\n    ```\n    <details>\n      <summary>💡 An alternative to heavyweight MacTeX bundle.</summary>\n\n      > To avoid installing the full MacTeX bundle, which is ~6GB, you can alternatively install the\n      > lightweight [BasicTeX](https://formulae.brew.sh/cask/basictex) and then gradually add\n      > only the LaTeX packages you actually need. A list of packages sufficient to run examples can \n      > be found [here](https://github.com/3b1b/manim/issues/2133#issuecomment-2414547866).\n      > For an overview of the MacTeX installer bundles, see https://www.tug.org/mactex/.\n    </details>\n\n2. If you are using an ARM-based processor, install Cairo. \n    ```sh\n    arch -arm64 brew install pkg-config cairo\n    ```\n   \n3. Install latest version of manim using these command.\n    ```sh\n    git clone https://github.com/3b1b/manim.git\n    cd manim\n    pip install -e .\n    manimgl example_scenes.py OpeningManimExample (make sure to add manimgl to path first.)\n    ```\n\n## Anaconda Install\n\n1. Install LaTeX as above.\n2. Create a conda environment using `conda create -n manim python=3.9`.\n3. Activate the environment using `conda activate manim`.\n4. Install manimgl using `pip install -e .`.\n\n\n## Using manim\nTry running the following:\n```sh\nmanimgl example_scenes.py OpeningManimExample\n```\nThis should pop up a window playing a simple scene.\n\nLook through the [example scenes](https://3b1b.github.io/manim/getting_started/example_scenes.html) to see examples of the library's syntax, animation types and object types. In the [3b1b/videos](https://github.com/3b1b/videos) repo, you can see all the code for 3blue1brown videos, though code from older videos may not be compatible with the most recent version of manim. The readme of that repo also outlines some details for how to set up a more interactive workflow, as shown in [this manim demo video](https://www.youtube.com/watch?v=rbu7Zu5X1zI) for example.\n\nWhen running in the CLI, some useful flags include:\n* `-w` to write the scene to a file\n* `-o` to write the scene to a file and open the result\n* `-s` to skip to the end and just show the final frame.\n    * `-so` will save the final frame to an image and show it\n* `-n <number>` to skip ahead to the `n`'th animation of a scene.\n* `-f` to make the playback window fullscreen\n\nTake a look at custom_config.yml for further configuration.  To add your customization, you can either edit this file, or add another file by the same name \"custom_config.yml\" to whatever directory you are running manim from.  For example [this is the one](https://github.com/3b1b/videos/blob/master/custom_config.yml) for 3blue1brown videos.  There you can specify where videos should be output to, where manim should look for image files and sounds you want to read in, and other defaults regarding style and video quality.\n\n### Documentation\nDocumentation is in progress at [3b1b.github.io/manim](https://3b1b.github.io/manim/). And there is also a Chinese version maintained by [**@manim-kindergarten**](https://manim.org.cn): [docs.manim.org.cn](https://docs.manim.org.cn/) (in Chinese).\n\n[manim-kindergarten](https://github.com/manim-kindergarten/) wrote and collected some useful extra classes and some codes of videos in [manim_sandbox repo](https://github.com/manim-kindergarten/manim_sandbox).\n\n\n## Contributing\nIs always welcome.  As mentioned above, the [community edition](https://github.com/ManimCommunity/manim) has the most active ecosystem for contributions, with testing and continuous integration, but pull requests are welcome here too.  Please explain the motivation for a given change and examples of its effect.\n\n\n## License\nThis project falls under the MIT license.\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line, and also\n# from the environment for the first two.\nSPHINXOPTS    ?=\nSPHINXBUILD   ?= sphinx-build\nSOURCEDIR     = source\nBUILDDIR      = build\n\n# Put it first so that \"make\" without argument is like \"make help\".\nhelp:\n\t@$(SPHINXBUILD) -M help \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n\n.PHONY: help Makefile\n\n# Catch-all target: route all unknown targets to Sphinx using the new\n# \"make mode\" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).\n%: Makefile\n\t@$(SPHINXBUILD) -M $@ \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n"
  },
  {
    "path": "docs/example.py",
    "content": "from manimlib import *\n\nclass SquareToCircle(Scene):\n    def construct(self):\n        circle = Circle()\n        circle.set_fill(BLUE, opacity=0.5)\n        circle.set_stroke(BLUE_E, width=4)\n        square = Square()\n\n        self.play(ShowCreation(square))\n        self.wait()\n        self.play(ReplacementTransform(square, circle))\n        self.wait()\n        # Try typing the following lines\n        # self.play(circle.animate.stretch(4, dim=0))\n        # self.play(Rotate(circle, TAU / 4))\n        # self.play(circle.animate.shift(2 * RIGHT), circle.animate.scale(0.25))\n        # circle.insert_n_curves(10)\n        # self.play(circle.animate.apply_complex_function(lambda z: z**2))\n\nclass SquareToCircleEmbed(Scene):\n    def construct(self):\n        circle = Circle()\n        circle.set_fill(BLUE, opacity=0.5)\n        circle.set_stroke(BLUE_E, width=4)\n\n        self.add(circle)\n        self.wait()\n        self.play(circle.animate.stretch(4, dim=0))\n        self.wait(1.5)\n        self.play(Rotate(circle, TAU / 4))\n        self.wait(1.5)\n        self.play(circle.animate.shift(2 * RIGHT), circle.animate.scale(0.25))\n        self.wait(1.5)\n        circle.insert_n_curves(10)\n        self.play(circle.animate.apply_complex_function(lambda z: z**2))\n        self.wait(2)\n"
  },
  {
    "path": "docs/make.bat",
    "content": "@ECHO OFF\n\npushd %~dp0\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-build\n)\nset SOURCEDIR=source\nset BUILDDIR=build\n\nif \"%1\" == \"\" goto help\n\n%SPHINXBUILD% >NUL 2>NUL\nif errorlevel 9009 (\n\techo.\n\techo.The 'sphinx-build' command was not found. Make sure you have Sphinx\n\techo.installed, then set the SPHINXBUILD environment variable to point\n\techo.to the full path of the 'sphinx-build' executable. Alternatively you\n\techo.may add the Sphinx directory to PATH.\n\techo.\n\techo.If you don't have Sphinx installed, grab it from\n\techo.http://sphinx-doc.org/\n\texit /b 1\n)\n\n%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%\ngoto end\n\n:help\n%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%\n\n:end\npopd\n"
  },
  {
    "path": "docs/requirements.txt",
    "content": "Sphinx==3.0.3\nsphinx-copybutton\nfuro==2020.10.5b9\nJinja2"
  },
  {
    "path": "docs/source/conf.py",
    "content": "import os\nimport sys\nsys.path.insert(0, os.path.abspath(\".\"))\nsys.path.insert(0, os.path.abspath('../../'))\n\n\nproject = 'manim'\ncopyright = '- This document has been placed in the public domain.'\nauthor = 'TonyCrane'\n\nrelease = ''\n\nextensions = [\n    'sphinx.ext.todo',\n    'sphinx.ext.githubpages',\n    'sphinx.ext.mathjax',\n    'sphinx.ext.intersphinx',\n    'sphinx.ext.autodoc', \n    'sphinx.ext.coverage',\n    'sphinx.ext.napoleon',\n    'sphinx_copybutton',\n    'manim_example_ext'\n]\n\nautoclass_content = 'both'\nmathjax_path = \"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js\"\n\ntemplates_path = ['_templates']\nsource_suffix = '.rst'\nmaster_doc = 'index'\npygments_style = 'default'\n\nhtml_static_path = [\"_static\"]\nhtml_css_files = [\n    \"https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/custom.css\", \n    \"https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/colors.css\"\n]\nhtml_theme = 'furo'  # pip install furo==2020.10.5b9\nhtml_favicon = '_static/icon.png'\nhtml_logo = '../../logo/transparent_graph.png'\nhtml_theme_options = {\n    \"sidebar_hide_name\": True,\n}\n"
  },
  {
    "path": "docs/source/development/about.rst",
    "content": "About\n=====\n\nAbout Manim\n-----------\n\nManim is an animation engine for explanatory math videos. \nYou can use it to make math videos (or other fields) like 3Blue1Brown.\n\nThere are mainly two versions here:\n\n- `3b1b/manim <https://github.com/3b1b/manim>`_ : Maintained by Grant Sanderson of 3Blue1Brown.\n\nUsing OpenGL and its GLSL language to use GPU for rendering. It has higher efficiency, \nfaster rendering speed, and supports real-time rendering and interaction.\n\n- `ManimCommunity/manim <https://github.com/ManimCommunity/manim>`_ : Maintained by Manim Community Dev Team.\n\nUsing multiple backend rendering. There is better documentation and \na more open contribution community.\n\nAbout this documentation\n------------------------\n\nThis documentation is based on the version in `3b1b/manim <https://github.com/3b1b/manim>`_. \nCreated by `TonyCrane <https://github.com/TonyCrane>`_ (\"鹤翔万里\" in Chinese) and in production.\n\nAmong them, the ``manim_example_ext`` extension for Sphinx refers to \n`the documentation of ManimCommunity <https://docs.manim.community/>`_.\n\nIf you want to contribute to manim or this document, please see: :doc:`contributing`"
  },
  {
    "path": "docs/source/development/changelog.rst",
    "content": "Changelog\n=========\n\nUnreleased\n----------\n\nBreaking Changes\n^^^^^^^^^^^^^^^^\n- Added ``InteractiveScene`` (`#1794 <https://github.com/3b1b/manim/pull/1794>`__)\n\nFixed bugs\n^^^^^^^^^^\n- Fixed ``ImageMobject`` by overriding ``set_color`` method (`#1791 <https://github.com/3b1b/manim/pull/1791>`__)\n- Fixed bug with trying to close window during embed (`#1796 <https://github.com/3b1b/manim/commit/e0f5686d667152582f052021cd62bd2ef8c6b470>`__)\n- Fixed animating ``Mobject.restore`` bug (`#1796 <https://github.com/3b1b/manim/commit/62289045cc8e102121cfe4d7739f3c89102046fb>`__)\n- Fixed ``InteractiveScene.refresh_selection_highlight`` (`#1802 <https://github.com/3b1b/manim/commit/205116b8cec964b5619416f6e8acf0d8ac7df828>`__)\n- Fixed ``VMobject.match_style`` (`#1821 <https://github.com/3b1b/manim/commit/0060a4860c9d6b073a60cd839269c213446bba7b>`__)\n\nNew Features\n^^^^^^^^^^^^\n- Added specific euler angle getters (`#1794 <https://github.com/3b1b/manim/commit/df2d465140e25fee265f602608aebbbaa2898c7e>`__)\n- Added start angle option to ``Circle`` (`#1794 <https://github.com/3b1b/manim/commit/217c1d7bb02f23a61722bf7275c40802be808563>`__)\n- Added ``Mobject.is_touching`` (`#1794 <https://github.com/3b1b/manim/commit/c1716895c0d9f36e23487322a18963991100bb95>`__)\n- Added ``Mobject.get_highlight`` (`#1794 <https://github.com/3b1b/manim/commit/29816fa74c7aa6ca060b63ab4165c89987e58d8b>`__)\n- Allowed for saving and loading mobjects from file (`#1794 <https://github.com/3b1b/manim/commit/50f5d20cc379947d7253d841c060dd7c55fa7787>`__)\n- Added ``Mobject.get_all_corners`` (`#1794 <https://github.com/3b1b/manim/commit/f636199d9a5d1e87ab861bcb6aebae6c9d96a133>`__)\n- Added ``Scene.id_to_mobject`` and ``Scene.ids_to_group`` (`#1794 <https://github.com/3b1b/manim/commit/cb768c26a0bc63e02c3035b4af31ba5cbc2e9dda>`__)\n- Added ``Scene.save_mobject`` and ``Scene.load_mobject`` to allow for saving and loading mobjects from file at the Scene level (`#1794 <https://github.com/3b1b/manim/commit/777b6d37783f8592df8a8abc3d62af972bc5a0c6>`__)\n- Added ``InteractiveScene`` (`#1794 <https://github.com/3b1b/manim/commit/c3afc84bfeb3a76ea8ede4ec4d9f36df0d4d9a28>`__)\n- Added ``VHighlight`` (`#1794 <https://github.com/3b1b/manim/commit/9d5e2b32fa9215219d11a601829126cea40410d1>`__)\n- Allowed for sweeping selection (`#1796 <https://github.com/3b1b/manim/commit/4caa03332367631d2fff15afd7e56b15fe8701ee>`__)\n- Allowed stretched-resizing (`#1796 <https://github.com/3b1b/manim/commit/b4b72d1b68d0993b96a6af76c4bb6816f77f0f12>`__)\n- Added cursor location label (`#1796 <https://github.com/3b1b/manim/commit/b9751e9d06068f27a327b419c52fd3c9d68db2e6>`__)\n- Added ``Mobject.deserialize`` (`#1796 <https://github.com/3b1b/manim/commit/4d8698a0e88333f6481c08d1b84b6e44f9dc4543>`__)\n- Added undo and redo stacks for scene (`#1796 <https://github.com/3b1b/manim/commit/cf466006faa00fc12dc22f5732dc21ccedaa5a63>`__)\n- Added ``Mobject.looks_identical`` (`#1802 <https://github.com/3b1b/manim/commit/c3c5717dde543b172b928b516d80a29bbd12651f>`__)\n- Added equality for ``ShaderWrapper`` (`#1802 <https://github.com/3b1b/manim/commit/3ae0a4e81b7790194bcf27142a1deb29fa548b9d>`__)\n- Added ``Mobject.get_ancestors`` (`#1802 <https://github.com/3b1b/manim/commit/db884b0a67fcee1ad7009f1869c475015fa886c7>`__)\n- Added smarter default radius to ``Polygon.round_corners`` (`#1802 <https://github.com/3b1b/manim/commit/4c1210b3ab1bf66b161f3d00cb859d36068c2fbb>`__)\n- Added checkpoints to ``Scene`` (`#1821 <https://github.com/3b1b/manim/commit/1b589e336f8151f2914ff00e8956baea8a95abc5>`__)\n- Added ``crosshair`` to ``InteractiveScene`` (`#1821 <https://github.com/3b1b/manim/commit/33ffd4863aaa7ecf950b7044181a8e8e3c643698>`__)\n- Added ``SceneState`` (`#1821 <https://github.com/3b1b/manim/commit/75e1cff5792065aa1c7fb3eb02e6ee0fa0e8e18d>`__)\n- Added ``time_span`` option to ``Animation`` (`#1821 <https://github.com/3b1b/manim/commit/a6fcfa3b4053b7f68f7b029eae87dbd207d97ad2>`__)\n- Added ``Mobject.arrange_to_fit_dim`` (`#1821 <https://github.com/3b1b/manim/commit/a87d3b5f59a64ce5a89ce6e17310bdbf62166157>`__)\n- Added ``DecimalNumber.get_tex`` (`#1821 <https://github.com/3b1b/manim/commit/48689c8c7bc0029bf5c1b540c11f647e857d419b>`__)\n\nRefactor\n^^^^^^^^\n- Updated parent updater status when adding updaters (`#1794 <https://github.com/3b1b/manim/commit/3b847da9eaad7391e779c5dbce63ad9257d8c773>`__)\n- Added case for zero vectors on ``angle_between_vectors`` (`#1794 <https://github.com/3b1b/manim/commit/e8ac25903e19cbb2b2c2037c988baafce4ddcbbc>`__)\n- Refactored ``Mobject.clear_updaters`` (`#1794 <https://github.com/3b1b/manim/commit/95f56f5e80106443d705c68fa220850ec38daee0>`__)\n- Changed the way changing-vs-static mobjects are tracked (more details see `#1794 <https://github.com/3b1b/manim/commit/50565fcd7a43ed13dc532f17515208edf97f64d0>`__)\n- Refactored ``Mobject.is_point_touching`` (`#1794 <https://github.com/3b1b/manim/commit/135f68de35712be266a1a85261d6d44234fc0056>`__)\n- Refactored ``Mobject.make_movable`` and ``Mobject.set_animating_status`` to recurse over family (`#1794 <https://github.com/3b1b/manim/commit/48390375037f745c9cb82b03d1cb3a1de6c530f3>`__)\n- Refactored ``AnimationGroup`` (`#1794 <https://github.com/3b1b/manim/commit/fdeab8ca953b46a902b531febcf132739ca194d4>`__)\n- Refactored ``Scene.save_state`` and ``Scene.restore`` (`#1794 <https://github.com/3b1b/manim/commit/97400a5cf26f33ed507ddeeb9b9a7f1a558d4f17>`__)\n- Added ``MANIM_COLORS`` (`#1794 <https://github.com/3b1b/manim/commit/5a34ca1fba8b4724eda0caa11b271d74e49f468c>`__)\n- Changed default transparent background codec to be prores (`#1794 <https://github.com/3b1b/manim/commit/eae7dbbe6eaf4344374713052aae694e69b62c28>`__)\n- Simplified ``Mobject.copy`` (`#1794 <https://github.com/3b1b/manim/commit/1b009a4b035244bd6a0b48bc4dc945fd3b4236ef>`__)\n- Refactored ``StringMobject`` and relevant classes (`#1795 <https://github.com/3b1b/manim/pull/1795>`__)\n- Updates to copying based on pickle serializing (`#1796 <https://github.com/3b1b/manim/commit/fe3e10acd29a3dd6f8b485c0e36ead819f2d937b>`)\n- Removed ``refresh_shader_wrapper_id`` from ``Mobject.become`` (`#1796 <https://github.com/3b1b/manim/commit/1b2460f02a694314897437b9b8755443ed290cc1>`__)\n- Refactored ``Scene.embed`` to play nicely with gui interactions (`#1796 <https://github.com/3b1b/manim/commit/c96bdc243e57c17bb75bf12d73ab5bf119cf1464>`__)\n- Made ``BlankScene`` inherit from ``InteractiveScene`` (`#1796 <https://github.com/3b1b/manim/commit/2737d9a736885a594dd101ffe07bb82e00069333>`__)\n- Updated behavior of -e flag to take in (optional) strings as inputs (`#1796 <https://github.com/3b1b/manim/commit/bb7fa2c8aa68d7c7992517cfde3c7d0e804e13e8>`__)\n- Refactor -e flag (`#1796 <https://github.com/3b1b/manim/commit/71c14969dffc8762a43f9646a0c3dc024a51b8df>`__)\n- Reverted to original copying scheme (`#1796 <https://github.com/3b1b/manim/commit/59506b89cc73fff3b3736245dd72e61dcebf9a2c>`__)\n- Renamed ``Mobject.is_movable`` to ``Mobject.interaction_allowed`` (`#1796 <https://github.com/3b1b/manim/commit/3961005fd708333a3e77856d10e78451faa04075>`__)\n- Refreshed static mobjects on undo's and redo's (`#1796 <https://github.com/3b1b/manim/commit/04bca6cafbb1482b8f25cfb34ce83316d8a095c9>`__)\n- Factored out event handling (`#1796 <https://github.com/3b1b/manim/commit/754316bf586be5a59839f8bac6fb9fcc47da0efb>`__)\n- Removed ``Mobject.interaction_allowed``, in favor of using ``_is_animating`` for multiple purposes (`#1796 <https://github.com/3b1b/manim/commit/f70e91348c8241bcb96470e7881dd92d9d3386d3>`__)\n- Moved Command + z and Command + shift + z behavior to Scene (`#1797 <https://github.com/3b1b/manim/commit/0fd8491c515ad23ca308099abe0f39fc38e2dd0e>`__)\n- Slight copy refactor (`#1797 <https://github.com/3b1b/manim/commit/902c2c002d6ca03c8080b2bd02ca36f2b8a748b6>`__)\n- When scene saves state, have it only copy mobjects which have changed (`#1802 <https://github.com/3b1b/manim/commit/bd2dce08300e5b110c6668bd6763f3918fcdc65e>`__)\n- Cleaned up ``Scene.remove`` function (`#1802 <https://github.com/3b1b/manim/commit/6310e2fb6414b01b3fe4be1d4d98525e34356b5e>`__)\n- Speed-ups to ``Mobject.copy`` (`#1802 <https://github.com/3b1b/manim/commit/e49e4b8373c13c7a888193aaf61955470acbe5d6>`__)\n- Slight speed-up to ``InteractiveScene.gather_selection`` (`#1802 <https://github.com/3b1b/manim/commit/f2b4245c134da577a2854732ec0331768d93ffbe>`__)\n- Only leave wait notes in presenter mode (`#1802 <https://github.com/3b1b/manim/commit/42d1f48c60d11caa043d5458e64bfceb31ea203f>`__)\n- Refactored ``remove_list_redundancies`` and ``list_update`` (`#1821 <https://github.com/3b1b/manim/commit/b920e7be7b85bc0bb0577e2f71c4320bb97b42d4>`__)\n- Match updaters in ``Mobject.become`` (`#1821 <https://github.com/3b1b/manim/commit/0e45b41fea5f22d136f62f4af2e0d892e61a12ce>`__)\n- Don't show animation progress bar by default (`#1821 <https://github.com/3b1b/manim/commit/52259af5df619d3f44fbaff4c43402b93d01be2f>`__)\n- Handle quitting during scene more gracefully (`#1821 <https://github.com/3b1b/manim/commit/e83ad785caaa1a1456e07b23f207469d335bbc0d>`__)\n- Made ``selection_highlight`` refresh with an updater (`#1821 <https://github.com/3b1b/manim/commit/ac08963feff24a1dd2e57f604b44ea0a18ab01f3>`__)\n- Refactored ``anims_from_play_args`` to ``prepare_animations`` which deprecating old style ``self.play(mob.method, ...)`` (`#1821 <https://github.com/3b1b/manim/commit/feab79c260498fd7757a304e24c617a4e51ba1df>`__)\n- Made presenter mode hold before first play call (`#1821 <https://github.com/3b1b/manim/commit/a9a151d4eff80cc37b9db0fe7117727aac45ba09>`__)\n- Update frame on all play calls when skipping animations, so as to provide a rapid preview during scene loading (`#1821 <https://github.com/3b1b/manim/commit/41b811a5e7c03f528d41555217106e62b287ca3b>`__)\n- Renamed frame_rate to fps (`#1821 <https://github.com/3b1b/manim/commit/6decb0c32aec21c09007f9a2b91aaa8e642ca848>`__)\n- Let default text alignment be decided in default_config (`#1821 <https://github.com/3b1b/manim/commit/83b4aa6b88b6c3defb19f204189681f5afbb219e>`__)\n\nDependencies\n^^^^^^^^^^^^\n- Added dependency on ``pyperclip`` (`#1794 <https://github.com/3b1b/manim/commit/e579f4c955844fba415b976c313f64d1bb0376d0>`__)\n\n\nv1.6.1\n------\n\nFixed bugs\n^^^^^^^^^^\n- Fixed the bug of ``MTex`` with multi-line tex string (`#1785 <https://github.com/3b1b/manim/pull/1785>`__)\n- Fixed ``interpolate`` (`#1788 <https://github.com/3b1b/manim/pull/1788>`__)\n- Fixed ``ImageMobject`` (`#1791 <https://github.com/3b1b/manim/pull/1791>`__)\n\nRefactor\n^^^^^^^^\n- Added ``\\overset`` as a special string in ``Tex`` (`#1783 <https://github.com/3b1b/manim/pull/1783>`__)\n- Added ``outer_interpolate`` to perform interpolation using ``np.outer`` on arrays (`#1788 <https://github.com/3b1b/manim/pull/1788>`__)\n\nv1.6.0\n------\n\nBreaking changes\n^^^^^^^^^^^^^^^^\n- **Python 3.6 is no longer supported** (`#1736 <https://github.com/3b1b/manim/pull/1736>`__)\n\nFixed bugs\n^^^^^^^^^^\n- Fixed the width of riemann rectangles (`#1762 <https://github.com/3b1b/manim/pull/1762>`__)\n- Bug fixed in cases where empty array is passed to shader (`#1764 <https://github.com/3b1b/manim/pull/1764/commits/fa38b56fd87f713657c7f778f39dca7faf15baa8>`__)\n- Fixed ``AddTextWordByWord`` (`#1772 <https://github.com/3b1b/manim/pull/1772>`__)\n- Fixed ``ControlsExample`` (`#1781 <https://github.com/3b1b/manim/pull/1781>`__)\n\n\nNew features\n^^^^^^^^^^^^\n- Added more functions to ``Text`` (details: `#1751 <https://github.com/3b1b/manim/pull/1751>`__)\n- Allowed ``interpolate`` to work on an array of alpha values (`#1764 <https://github.com/3b1b/manim/pull/1764/commits/bf2d9edfe67c7e63ac0107d1d713df7ae7c3fb8f>`__)\n- Allowed ``Numberline.number_to_point`` and ``CoordinateSystem.coords_to_point`` to work on an array of inputs (`#1764 <https://github.com/3b1b/manim/pull/1764/commits/c3e13fff0587d3bb007e71923af7eaf9e4926560>`__)\n- Added a basic ``Prismify`` to turn a flat ``VMobject`` into something with depth (`#1764 <https://github.com/3b1b/manim/pull/1764/commits/f249da95fb65ed5495cd1db1f12ece7e90061af6>`__)\n- Added ``GlowDots``, analogous to ``GlowDot`` (`#1764 <https://github.com/3b1b/manim/pull/1764/commits/e19f35585d817e74b40bc30b1ab7cee84b24da05>`__)\n- Added ``TransformMatchingStrings`` which is compatible with ``Text`` and ``MTex`` (`#1772 <https://github.com/3b1b/manim/pull/1772>`__)\n- Added support for ``substring`` and ``case_sensitive`` parameters for ``LabelledString.get_parts_by_string`` (`#1780 <https://github.com/3b1b/manim/pull/1780>`__) \n\n\nRefactor\n^^^^^^^^\n- Added type hints (`#1736 <https://github.com/3b1b/manim/pull/1736>`__)\n- Specifid UTF-8 encoding for tex files (`#1748 <https://github.com/3b1b/manim/pull/1748>`__)\n- Refactored ``Text`` with the latest manimpango (`#1751 <https://github.com/3b1b/manim/pull/1751>`__)\n- Reorganized getters for ``ParametricCurve`` (`#1757 <https://github.com/3b1b/manim/pull/1757>`__)\n- Refactored ``CameraFrame`` to use ``scipy.spatial.transform.Rotation`` (`#1764 <https://github.com/3b1b/manim/pull/1764/commits/625460467fdc01fc1b6621cbb3d2612195daedb9>`__)\n- Refactored rotation methods to use ``scipy.spatial.transform.Rotation`` (`#1764 <https://github.com/3b1b/manim/pull/1764/commits/7bf3615bb15cc6d15506d48ac800a23313054c8e>`__)\n- Used ``stroke_color`` to init ``Arrow`` (`#1764 <https://github.com/3b1b/manim/pull/1764/commits/c0b7b55e49f06b75ae133b5a810bebc28c212cd6>`__)\n- Refactored ``Mobject.set_rgba_array_by_color`` (`#1764 <https://github.com/3b1b/manim/pull/1764/commits/8b1f0a8749d91eeda4b674ed156cbc7f8e1e48a8>`__)\n- Made panning more sensitive to mouse movements (`#1764 <https://github.com/3b1b/manim/pull/1764/commits/9d0cc810c5fcb4252990e706c6bf880d571cb1a2>`__)\n- Added loading progress for large SVGs (`#1766 <https://github.com/3b1b/manim/pull/1766>`__)\n- Added getter/setter of ``field_of_view`` for ``CameraFrame`` (`#1770 <https://github.com/3b1b/manim/pull/1770/commits/0610f331a4f7a126a3aae34f8a2a86eabcb692f4>`__)\n- Renamed ``focal_distance`` to ``focal_dist_to_height`` and added getter/setter (`#1770 <https://github.com/3b1b/manim/pull/1770/commits/0610f331a4f7a126a3aae34f8a2a86eabcb692f4>`__)\n- Added getter and setter for ``VMobject.joint_type`` (`#1770 <https://github.com/3b1b/manim/pull/1770/commits/2a7a7ac5189a14170f883533137e8a2ae09aac41>`__)\n- Refactored ``VCube`` (`#1770 <https://github.com/3b1b/manim/pull/1770/commits/0f8d7ed59751d42d5011813ba5694ecb506082f7>`__)\n- Refactored ``Prism`` to receive ``width height depth`` instead of ``dimensions`` (`#1770 <https://github.com/3b1b/manim/pull/1770/commits/0f8d7ed59751d42d5011813ba5694ecb506082f7>`__)\n- Refactored ``Text``, ``MarkupText`` and ``MTex`` based on ``LabelledString`` (`#1772 <https://github.com/3b1b/manim/pull/1772>`__)\n- Refactored ``LabelledString`` and relevant classes (`#1779 <https://github.com/3b1b/manim/pull/1779>`__)\n\n\nv1.5.0\n------\n\nFixed bugs\n^^^^^^^^^^\n- Bug fix for the case of calling ``Write`` on a null object (`#1740 <https://github.com/3b1b/manim/pull/1740>`__)\n\n\nNew features\n^^^^^^^^^^^^\n- Added ``TransformMatchingMTex`` (`#1725 <https://github.com/3b1b/manim/pull/1725>`__)\n- Added ``ImplicitFunction`` (`#1727 <https://github.com/3b1b/manim/pull/1727>`__)\n- Added ``Polyline`` (`#1731 <https://github.com/3b1b/manim/pull/1731>`__)\n- Allowed ``Mobject.set_points`` to take in an empty list, and added ``Mobject.add_point`` (`#1739 <https://github.com/3b1b/manim/pull/1739/commits/a64259158538eae6043566aaf3d3329ff4ac394b>`__)\n- Added ``Scene.refresh_locked_data`` (`#1739 <https://github.com/3b1b/manim/pull/1739/commits/33d2894c167c577a15fdadbaf26488ff1f5bff87>`__)\n- Added presenter mode to scenes with ``-p`` option (`#1739 <https://github.com/3b1b/manim/pull/1739/commits/9a9cc8bdacb7541b7cd4a52ad705abc21f3e27fe>`__ and `#1742 <https://github.com/3b1b/manim/pull/1742>`__)\n- Allowed for an embed by hitting ``ctrl+shift+e`` during interaction (`#1739 <https://github.com/3b1b/manim/pull/1739/commits/9df12fcb7d8360e51cd7021d6877ca1a5c31835e>`__ and `#1746 <https://github.com/3b1b/manim/pull/1746>`__)\n- Added ``Mobject.set_min_width/height/depth`` (`#1739 <https://github.com/3b1b/manim/pull/1739/commits/2798d15591a0375ae6bb9135473e6f5328267323>`__)\n- Allowed ``Mobject.match_coord/x/y/z`` to take in a point (`#1739 <https://github.com/3b1b/manim/pull/1739/commits/29a4d3e82ba94c007c996b2d1d0f923941452698>`__)\n- Added ``text_config`` to ``DecimalNumber`` (`#1744 <https://github.com/3b1b/manim/pull/1744>`__)\n\n\nRefactor\n^^^^^^^^\n- Refactored ``MTex`` (`#1725 <https://github.com/3b1b/manim/pull/1725>`__)\n- Refactored ``SVGMobject`` with svgelements (`#1731 <https://github.com/3b1b/manim/pull/1731>`__)\n- Made sure ``ParametricCurve`` has at least one point (`#1739 <https://github.com/3b1b/manim/pull/1739/commits/2488b9e866fb1ecb842a27dd9f4956ec167e3dee>`__)\n- Set default to no tips on ``Axes`` (`#1739 <https://github.com/3b1b/manim/pull/1739/commits/6c6d387a210756c38feca7d34838aa9ac99bb58a>`__)\n- Stopped displaying when writing tex string is happening (`#1739 <https://github.com/3b1b/manim/pull/1739/commits/58e06e8f6b7c5059ff315d51fd0018fec5cfbb05>`__)\n- Reorganize inheriting order and refactor SVGMobject (`#1745 <https://github.com/3b1b/manim/pull/1745>`__)\n\n\nDependencies\n^^^^^^^^^^^^\n- Added dependency on ``isosurfaces`` (`#1727 <https://github.com/3b1b/manim/pull/1727>`__)\n- Removed dependency on ``argparse`` since it's a built-in module (`#1728 <https://github.com/3b1b/manim/pull/1728>`__)\n- Removed dependency on ``pyreadline`` (`#1728 <https://github.com/3b1b/manim/pull/1728>`__)\n- Removed dependency on ``cssselect2`` (`#1731 <https://github.com/3b1b/manim/pull/1731>`__)\n- Added dependency on ``svgelements`` (`#1731 <https://github.com/3b1b/manim/pull/1731>`__)\n\n\nv1.4.1\n------\n\nFixed bugs \n^^^^^^^^^^\n- Temporarily fixed boolean operations' bug  (`#1724 <https://github.com/3b1b/manim/pull/1724>`__)\n- Import ``Iterable`` from ``collections.abc`` instead of ``collections`` which is deprecated since python 3.9 (`d2e0811 <https://github.com/3b1b/manim/commit/d2e0811285f7908e71a65e664fec88b1af1c6144>`__)\n\nv1.4.0\n------\n\nFixed bugs\n^^^^^^^^^^\n- Temporarily fixed ``Lightbulb`` (`f1996f8 <https://github.com/3b1b/manim/pull/1697/commits/f1996f8479f9e33d626b3b66e9eb6995ce231d86>`__)\n- Fixed some bugs of ``SVGMobject`` (`#1712 <https://github.com/3b1b/manim/pull/1712>`__)\n- Fixed some bugs of SVG path string parser (`#1717 <https://github.com/3b1b/manim/pull/1717>`__)\n- Fixed some bugs of ``MTex`` (`#1720 <https://github.com/3b1b/manim/pull/1720>`__)\n\nNew features\n^^^^^^^^^^^^\n- Added option to add ticks on x-axis in ``BarChart`` (`#1694 <https://github.com/3b1b/manim/pull/1694>`__)\n- Added ``lable_buff`` config parameter for ``Brace`` (`#1704 <https://github.com/3b1b/manim/pull/1704>`__)\n- Added support for ``rotate skewX skewY`` transform in SVG  (`#1712 <https://github.com/3b1b/manim/pull/1712>`__)\n- Added style support to ``SVGMobject`` (`#1717 <https://github.com/3b1b/manim/pull/1717>`__)\n- Added parser to <style> element of SVG  (`#1719 <https://github.com/3b1b/manim/pull/1719>`__)\n- Added support for <line> element in ``SVGMobject`` (`#1719 <https://github.com/3b1b/manim/pull/1719>`__)\n\nRefactor \n^^^^^^^^\n- Used ``FFMPEG_BIN`` instead of ``\"ffmpeg\"`` for sound incorporation (`5aa8d15 <https://github.com/3b1b/manim/pull/1697/commits/5aa8d15d85797f68a8f169ca69fd90d441a3abbe>`__)\n- Decorated ``CoordinateSystem.get_axes`` and ``.get_all_ranges`` as abstract method  (`#1709 <https://github.com/3b1b/manim/pull/1709>`__)\n- Refactored SVG path string parser (`#1712 <https://github.com/3b1b/manim/pull/1712>`__)\n- Allowed ``Mobject.scale`` to receive iterable ``scale_factor`` (`#1712 <https://github.com/3b1b/manim/pull/1712>`__)\n- Refactored ``MTex`` (`#1716 <https://github.com/3b1b/manim/pull/1716>`__)\n- Improved config helper (``manimgl --config``) (`#1721 <https://github.com/3b1b/manim/pull/1721>`__)\n- Refactored ``MTex`` (`#1723 <https://github.com/3b1b/manim/pull/1723>`__)\n\nDependencies\n^^^^^^^^^^^^\n- Added dependency on python package `cssselect2 <https://github.com/Kozea/cssselect2>`__ (`#1719 <https://github.com/3b1b/manim/pull/1719>`__)\n\n\nv1.3.0\n------\n\nFixed bugs \n^^^^^^^^^^\n\n- Fixed ``Mobject.stretch_to_fit_depth`` (`#1653 <https://github.com/3b1b/manim/pull/1653>`__)\n- Fixed the bug of rotating camera (`#1655 <https://github.com/3b1b/manim/pull/1655>`__)\n- Fixed ``SurfaceMesh`` to be evenly spaced (`c73d507 <https://github.com/3b1b/manim/pull/1688/commits/c73d507c76af5c8602d4118bc7538ba04c03ebae>`__)\n- Fixed ``angle_between_vectors`` add ``rotation_between_vectors`` (`82bd02d <https://github.com/3b1b/manim/pull/1688/commits/82bd02d21fbd89b71baa21e077e143f440df9014>`__)\n- Fixed ``VMobject.fade`` (`a717314 <https://github.com/3b1b/manim/pull/1688/commits/a7173142bf93fd309def0cc10f3c56f5e6972332>`__)\n- Fixed ``angle_between_vectors`` (`fbc329d <https://github.com/3b1b/manim/pull/1688/commits/fbc329d7ce3b11821d47adf6052d932f7eff724a>`__)\n- Fixed bug in ``ShowSubmobjectsOneByOne`` (`bcd0990 <https://github.com/3b1b/manim/pull/1688/commits/bcd09906bea5eaaa5352e7bee8f3153f434cf606>`__)\n- Fixed bug in ``TransformMatchingParts`` (`7023548 <https://github.com/3b1b/manim/pull/1691/commits/7023548ec62c4adb2f371aab6a8c7f62deb7c33c>`__)\n\nNew features\n^^^^^^^^^^^^\n\n- Added CLI flag ``--log-level`` to specify log level (`e10f850 <https://github.com/3b1b/manim/commit/e10f850d0d9f971931cc85d44befe67dc842af6d>`__)\n- Added operations (``+`` and ``*``) for ``Mobject`` (`#1667 <https://github.com/3b1b/manim/pull/1667>`__)\n- Added 4 boolean operations for ``VMobject`` in ``manimlib/mobject/boolean_ops.py`` (`#1675 <https://github.com/3b1b/manim/pull/1675>`__)\n\n  - ``Union(*vmobjects, **kwargs)``  \n  - ``Difference(subject, clip, **kwargs)`` \n  - ``Intersection(*vmobjects, **kwargs)`` \n  - ``Exclusion(*vmobjects, **kwargs)`` \n- Added reflectiveness (`81c3ae3 <https://github.com/3b1b/manim/pull/1688/commits/81c3ae30372e288dc772633dbd17def6e603753e>`__)\n- Enabled ``glow_factor`` on ``DotCloud`` (`2c7689e <https://github.com/3b1b/manim/pull/1688/commits/2c7689ed9e81229ce87c648f97f26267956c0bc9>`__)\n- Added option ``-e`` to insert embed line from the command line (`d065e19 <https://github.com/3b1b/manim/pull/1688/commits/d065e1973d1d6ebd2bece81ce4bdf0c2fff7c772>`__)\n- Improved ``point_from_proportion`` to account for arc length (`0e78027 <https://github.com/3b1b/manim/pull/1688/commits/0e78027186a976f7e5fa8d586f586bf6e6baab8d>`__)\n- Added shortcut ``set_backstroke`` for setting black background stroke (`781a993 <https://github.com/3b1b/manim/pull/1688/commits/781a9934fda6ba11f22ba32e8ccddcb3ba78592e>`__)\n- Added ``Suface.always_sort_to_camera`` (`0b898a5 <https://github.com/3b1b/manim/pull/1688/commits/0b898a5594203668ed9cad38b490ab49ba233bd4>`__)\n- Added getter methods for specific euler angles (`e899604 <https://github.com/3b1b/manim/pull/1688/commits/e899604a2d05f78202fcb3b9824ec34647237eae>`__)\n- Hade ``rotation_between_vectors`` handle identical/similar vectors (`407c53f <https://github.com/3b1b/manim/pull/1688/commits/407c53f97c061bfd8a53beacd88af4c786f9e9ee>`__)\n- Added ``Mobject.insert_submobject`` method (`49743da <https://github.com/3b1b/manim/pull/1688/commits/49743daf3244bfa11a427040bdde8e2bb79589e8>`__)\n- Created single progress display for full scene render (`9dd1f47 <https://github.com/3b1b/manim/pull/1688/commits/9dd1f47dabca1580d6102e34e44574b0cba556e7>`__)\n- Added ``Circle.get_radius`` (`264f7b1 <https://github.com/3b1b/manim/pull/1691/commits/264f7b11726e9e736f0fe472f66e38539f74e848>`__)\n- Added ``Dodecahedron`` (`83841ae <https://github.com/3b1b/manim/pull/1691/commits/83841ae41568a9c9dff44cd163106c19a74ac281>`__)\n- Added ``GlowDot`` (`a1d5147 <https://github.com/3b1b/manim/pull/1691/commits/a1d51474ea1ce3b7aa3efbe4c5e221be70ee2f5b>`__)\n- Added ``MTex`` , see `#1678 <https://github.com/3b1b/manim/pull/1678>`__ for details (`#1678 <https://github.com/3b1b/manim/pull/1678>`__)\n\nRefactor\n^^^^^^^^\n\n- Refactored support for command ``A`` in path of SVG  (`#1662 <https://github.com/3b1b/manim/pull/1662>`__)\n- Refactored ``SingleStringTex.balance_braces`` (`#1662 <https://github.com/3b1b/manim/pull/1662>`__)\n- Slight tweaks to how saturation_factor works on newton-fractal (`8b454fb <https://github.com/3b1b/manim/pull/1688/commits/8b454fbe9335a7011e947093230b07a74ba9c653>`__)\n- Made it possible to set full screen preview as a default (`317a5d6 <https://github.com/3b1b/manim/pull/1688/commits/317a5d6226475b6b54a78db7116c373ef84ea923>`__)\n- Used ``quick_point_from_proportion`` for graph points (`e764da3 <https://github.com/3b1b/manim/pull/1688/commits/e764da3c3adc5ae2a4ce877b340d2b6abcddc2fc>`__)\n- Made sure ``Line.set_length`` returns self (`d2182b9 <https://github.com/3b1b/manim/pull/1688/commits/d2182b9112300558b6c074cefd685f97c10b3898>`__)\n- Better align ``SurfaceMesh`` to the corresponding surface polygons (`eea3c6b <https://github.com/3b1b/manim/pull/1688/commits/eea3c6b29438f9e9325329c4355e76b9f635e97a>`__)\n- Match ``fix_in_frame`` status for ``FlashAround`` mobject (`ee1594a <https://github.com/3b1b/manim/pull/1688/commits/ee1594a3cb7a79b8fc361e4c4397a88c7d20c7e3>`__)\n- Made sure ``Mobject.is_fixed_in_frame`` stays updated with uniforms (`ba23fbe <https://github.com/3b1b/manim/pull/1688/commits/ba23fbe71e4a038201cd7df1d200514ed1c13bc2>`__)\n- Made sure ``skip_animations`` and ``start_at_animation_number`` play well together (`98b0d26 <https://github.com/3b1b/manim/pull/1691/commits/98b0d266d2475926a606331923cca3dc1dea97ad>`__)\n- Updated progress display for full scene render (`f8e6e7d <https://github.com/3b1b/manim/pull/1691/commits/f8e6e7df3ceb6f3d845ced4b690a85b35e0b8d00>`__)\n- ``VectorizedPoint`` should call ``__init__`` for both super classes (`8f1dfab <https://github.com/3b1b/manim/pull/1691/commits/8f1dfabff04a8456f5c4df75b0f97d50b2755003>`__)\n- Used array copy when checking need for refreshing triangulation (`758f329 <https://github.com/3b1b/manim/pull/1691/commits/758f329a06a0c198b27a48c577575d94554305bf>`__)\n\n\nDependencies\n^^^^^^^^^^^^\n\n- Added dependency on python package `skia-pathops <https://github.com/fonttools/skia-pathops>`__ (`#1675 <https://github.com/3b1b/manim/pull/1675>`__)\n\nv1.2.0\n------\n\nFixed bugs\n^^^^^^^^^^\n\n- Fixed ``put_start_and_end_on`` in 3D (`#1592 <https://github.com/3b1b/manim/pull/1592>`__)\n- Fixed ``DecimalNumber``'s scaling issue (`#1601 <https://github.com/3b1b/manim/pull/1601>`__)\n- Fixed bug with common range array used for all coordinate systems (`56df154 <https://github.com/3b1b/manim/commit/56df15453f3e3837ed731581e52a1d76d5692077>`__)\n- Fixed ``CoordinateSystem`` init bug (`8645894 <https://github.com/3b1b/manim/commit/86458942550c639a241267d04d57d0e909fcf252>`__)\n- Fixed bug for single-valued ``ValueTracker`` (`0dc096b <https://github.com/3b1b/manim/commit/0dc096bf576ea900b351e6f4a80c13a77676f89b>`__)\n- Fixed bug with SVG rectangles (`54ad355 <https://github.com/3b1b/manim/commit/54ad3550ef0c0e2fda46b26700a43fa8cde0973f>`__)\n- Fixed ``DotCloud.set_radii`` (`d45ea28 <https://github.com/3b1b/manim/commit/d45ea28dc1d92ab9c639a047c00c151382eb0131>`__)\n- Temporarily fixed bug for ``PMobject`` array resizing (`b543cc0 <https://github.com/3b1b/manim/commit/b543cc0e32d45399ee81638b6d4fb631437664cd>`__)\n- Fixed ``match_style`` (`5f878a2 <https://github.com/3b1b/manim/commit/5f878a2c1aa531b7682bd048468c72d2835c7fe5>`__)\n- Fixed negative ``path_arc`` case (`719c81d <https://github.com/3b1b/manim/commit/719c81d72b00dcf49f148d7c146774b22e0fe348>`__)\n- Fixed bug with ``CoordinateSystem.get_lines_parallel_to_axis`` (`c726eb7 <https://github.com/3b1b/manim/commit/c726eb7a180b669ee81a18555112de26a8aff6d6>`__)\n- Fixed ``ComplexPlane`` -i display bug (`7732d2f <https://github.com/3b1b/manim/commit/7732d2f0ee10449c5731499396d4911c03e89648>`__)\n\nNew features \n^^^^^^^^^^^^\n\n- Supported the elliptical arc command ``A`` for ``SVGMobject`` (`#1598 <https://github.com/3b1b/manim/pull/1598>`__)\n- Added ``FlashyFadeIn`` (`#1607 <https://github.com/3b1b/manim/pull/1607>`__)\n- Save triangulation  (`#1607 <https://github.com/3b1b/manim/pull/1607>`__)\n- Added new ``Code`` mobject (`#1625 <https://github.com/3b1b/manim/pull/1625>`__)\n- Add warnings and use rich to display log (`#1637 <https://github.com/3b1b/manim/pull/1637>`__)\n- Added ``VCube`` (`bd356da <https://github.com/3b1b/manim/commit/bd356daa99bfe3134fcb192a5f72e0d76d853801>`__)\n- Supported ``ValueTracker`` to track vectors (`6d72893 <https://github.com/3b1b/manim/commit/6d7289338234acc6658b9377c0f0084aa1fa7119>`__)\n- Added ``set_max_width``, ``set_max_height``, ``set_max_depth`` to ``Mobject`` (`3bb8f3f <https://github.com/3b1b/manim/commit/3bb8f3f0422a5dfba0da6ef122dc0c01f31aff03>`__)\n- Added ``TracgTail`` (`a35dd5a <https://github.com/3b1b/manim/commit/a35dd5a3cbdeffa3891d5aa5f80287c18dba2f7f>`__)\n- Added ``Scene.point_to_mobject`` (`acba13f <https://github.com/3b1b/manim/commit/acba13f4991b78d54c0bf93cce7ca3b351c25476>`__)\n- Added poly_fractal shader (`f84b8a6 <https://github.com/3b1b/manim/commit/f84b8a66fe9e8b3872e5c716c5c240c14bb555ee>`__)\n- Added kwargs to ``TipableVMobject.set_length`` (`b24ba19 <https://github.com/3b1b/manim/commit/b24ba19dec48ba4e38acbde8eec6d3a308b6ab83>`__)\n- Added ``Mobject.replicate`` (`17c2772 <https://github.com/3b1b/manim/commit/17c2772b84abf6392a4170030e36e981de4737d0>`__)\n- Added mandelbrot_fractal shader (`33fa76d <https://github.com/3b1b/manim/commit/33fa76dfac36e70bb5fad69dc6a336800c6dacce>`__)\n- Saved state before each embed (`f22a341 <https://github.com/3b1b/manim/commit/f22a341e8411eae9331d4dd976b5e15bc6db08d9>`__)\n- Allowed releasing of Textures (`e10a752 <https://github.com/3b1b/manim/commit/e10a752c0001e8981038faa03be4de2603d3565f>`__)\n- Consolidated and renamed newton_fractal shader (`14fbed7 <https://github.com/3b1b/manim/commit/14fbed76da4b493191136caebb8a955e2d41265b>`__)\n- Hade ``ImageMoject`` remember the filepath to the Image (`6cdbe0d <https://github.com/3b1b/manim/commit/6cdbe0d67a11ab14a6d84840a114ae6d3af10168>`__)\n\nRefactor\n^^^^^^^^\n\n- Changed back to simpler ``Mobject.scale`` implementation (`#1601 <https://github.com/3b1b/manim/pull/1601>`__)\n- Simplified ``Square`` (`b667db2 <https://github.com/3b1b/manim/commit/b667db2d311a11cbbca2a6ff511d2c3cf1675486>`__)\n- Removed unused parameter ``triangulation_locked`` (`40290ad <https://github.com/3b1b/manim/commit/40290ada8343f10901fa9151cbdf84689667786d>`__)\n- Reimplemented ``Arrow`` (`8647a64 <https://github.com/3b1b/manim/commit/8647a6429dd0c52cba14e971b8c09194a93cfd87>`__)\n- Used ``make_approximately_smooth`` for ``set_points_smoothly`` by default (`d8378d8 <https://github.com/3b1b/manim/commit/d8378d8157040cd797cc47ef9576beffd8607863>`__)\n- Refactored to call ``_handle_scale_side_effects`` after scaling takes place (`7b4199c <https://github.com/3b1b/manim/commit/7b4199c674e291f1b84678828b63b6bd4fcc6b17>`__)\n- Refactored to only call ``throw_error_if_no_points`` once for ``get_start_and_end`` (`7356a36 <https://github.com/3b1b/manim/commit/7356a36fa70a8279b43ae74e247cbd43b2bfd411>`__)\n- Made sure framerate is 30 for previewed scenes (`0787c4f <https://github.com/3b1b/manim/commit/0787c4f36270a6560b50ce3e07b30b0ec5f2ba3e>`__)\n- Pushed ``pixel_coords_to_space_coords`` to ``Window`` (`c635f19 <https://github.com/3b1b/manim/commit/c635f19f2a33e916509e53ded46f55e2afa8f5f2>`__)\n- Refactored to pass tuples and not arrays to uniforms (`d5a88d0 <https://github.com/3b1b/manim/commit/d5a88d0fa457cfcf4cb9db417a098c37c95c7051>`__)\n- Refactored to copy uniform arrays in ``Mobject.copy`` (`9483f26 <https://github.com/3b1b/manim/commit/9483f26a3b056de0e34f27acabd1a946f1adbdf9>`__)\n- Added ``bounding_box`` as exceptional key to point_cloud mobject (`ed1fc4d <https://github.com/3b1b/manim/commit/ed1fc4d5f94467d602a568466281ca2d0368b506>`__)\n- Made sure stroke width is always a float (`329d2c6 <https://github.com/3b1b/manim/commit/329d2c6eaec3d88bfb754b555575a3ea7c97a7e0>`__)\n\n\nv1.1.0\n-------\n\nFixed bugs\n^^^^^^^^^^\n\n- Fixed the bug of :func:`~manimlib.utils.iterables.resize_with_interpolation` in the case of ``length=0``\n- Fixed the bug of ``__init__`` in :class:`~manimlib.mobject.geometry.Elbow`\n- If chosen monitor is not available, choose one that does exist\n- Make sure mobject data gets unlocked after animations\n- Fixed a bug for off-center vector fields\n- Had ``Mobject.match_points`` return self\n- Fixed chaining animation in example scenes\n- Fixed the default color of tip\n- Fixed a typo in ``ShowPassingFlashWithThinningStrokeWidth``\n- Fixed the default size of ``Text``\n- Fixed a missing import line in ``mobject.py``\n- Fixed the bug in ControlsExample\n- Make sure frame is added to the scene when initialization\n- Fixed zooming directions\n- Rewrote ``earclip_triangulation`` to fix triangulation\n- Allowed sound_file_name to be taken in without extensions\n\nNew features\n^^^^^^^^^^^^\n\n- Added :class:`~manimlib.animation.indication.VShowPassingFlash`\n- Added ``COLORMAP_3B1B``\n- Added some methods to coordinate system to access all axes ranges\n  \n  - :meth:`~manimlib.mobject.coordinate_systems.CoordinateSystem.get_origin`\n  - :meth:`~manimlib.mobject.coordinate_systems.CoordinateSystem.get_all_ranges`\n- Added :meth:`~manimlib.mobject.mobject.Mobject.set_color_by_rgba_func`\n- Updated :class:`~manimlib.mobject.vector_field.VectorField` and :class:`~manimlib.mobject.vector_field.StreamLines`\n- Allow ``3b1b_colormap`` as an option for :func:`~manimlib.utils.color.get_colormap_list`\n- Return ``stroke_width`` as 1d array\n- Added :meth:`~manimlib.mobject.svg.text_mobject.Text.get_parts_by_text`\n- Use Text not TexText for Brace\n- Update to Cross to make it default to variable stroke width\n- Added :class:`~manimlib.animation.indication.FlashAround` and :class:`~manimlib.animation.indication.FlashUnder`\n- Allowed configuration in ``Brace.get_text``\n- Added :meth:`~manimlib.camera.camera.CameraFrame.reorient` for quicker changes to frame angle\n- Added ``units`` to :meth:`~manimlib.camera.camera.CameraFrame.set_euler_angles`\n- Allowed any ``VMobject`` to be passed into ``TransformMatchingTex``\n- Removed double brace convention in ``Tex`` and ``TexText``\n- Added support for debugger launch\n- Added CLI flag ``--config_file`` to load configuration file manually\n- Added ``tip_style`` to ``tip_config``\n- Added ``MarkupText``\n- Take in ``u_range`` and ``v_range`` as arguments to ``ParametricSurface``\n- Added ``TrueDot``"
  },
  {
    "path": "docs/source/development/contributing.rst",
    "content": "Contributing\n============\n\nAccept any contribution you make :)\n\n- **Contribute to the manim source code**: \n\nPlease fork to your own repository and make changes, submit a pull request, and fill in \nthe motivation for the change following the instructions in the template. We will check \nyour pull request in detail (this usually takes a while, please be patient)\n\n- **Contribute to the documentation**: \n\nAlso submit a pull request and write down the main changes.\n\n- **If you find a bug in the code**: \n\nPlease open an issue and fill in the found problem and your environment according \nto the template. (But please note that if you think this problem is just a problem \nof yourself, rather than a problem of source code, it is recommended that you ask a \nquestion in the `Q&A category <https://github.com/3b1b/manim/discussions/categories/q-a>`_ \nof the discussion page)\n\n- **You are welcome to share the content you made with manim**: \n\nPost it in the `show and tell category <https://github.com/3b1b/manim/discussions/categories/show-and-tell>`_\nof the discussion page.\n\n- **You are also welcome to share some of your suggestions and ideas**: \n\nPost them in the `ideas category <https://github.com/3b1b/manim/discussions/categories/ideas>`_ \nof the discussion page.\n\nHow to build this documentation\n-------------------------------\n\n- Clone the 3b1b/manim repository\n\n.. code-block:: sh\n\n    git clone https://github.com/3b1b/manim.git\n    # or your own repo\n    # git clone https://github.com/<your user name>/manim.git\n    cd manim\n\n- Install python package dependencies\n\n.. code-block:: sh\n\n    pip install -r docs/requirements.txt\n\n- Go to the ``docs/`` folder and build\n\n.. code-block:: sh\n\n    cd docs/\n    make html\n\n- The output document is located in ``docs/build/html/``"
  },
  {
    "path": "docs/source/documentation/animation/index.rst",
    "content": "Animation (TODO)\n================"
  },
  {
    "path": "docs/source/documentation/camera/index.rst",
    "content": "Camera (TODO)\n============="
  },
  {
    "path": "docs/source/documentation/constants.rst",
    "content": "constants\n=========\n\nThe ``constants.py`` in the ``manimlib`` folder defines the constants \nneeded when running manim. Some constants are not explained here because \nthey are only used inside manim.\n\nFrame and pixel shape\n---------------------\n\nThese values will be determined based on the ``camera`` configuration in default_config.yml or custom_config.yml\n\n.. code-block:: python\n\n    ASPECT_RATIO\n    FRAME_HEIGHT\n    FRAME_WIDTH\n    FRAME_Y_RADIUS\n    FRAME_X_RADIUS\n\n    DEFAULT_PIXEL_HEIGHT\n    DEFAULT_PIXEL_WIDTH\n    DEFAULT_FPS\n\nBuffs\n-----\n\nThese values will be determined based on the ``size`` configuration in default_config.yml or custom_config.yml\n\n\n.. code-block:: python\n\n    SMALL_BUFF\n    MED_SMALL_BUFF\n    MED_LARGE_BUFF\n    LARGE_BUFF\n\n    DEFAULT_MOBJECT_TO_EDGE_BUFF\n    DEFAULT_MOBJECT_TO_MOBJECT_BUFF\n\nCoordinates\n-----------\n\nmanim uses three-dimensional coordinates and uses the type of ``ndarray``\n\n.. code-block:: python\n\n    ORIGIN = np.array((0., 0., 0.))\n    UP = np.array((0., 1., 0.))\n    DOWN = np.array((0., -1., 0.))\n    RIGHT = np.array((1., 0., 0.))\n    LEFT = np.array((-1., 0., 0.))\n    IN = np.array((0., 0., -1.))\n    OUT = np.array((0., 0., 1.))\n    X_AXIS = np.array((1., 0., 0.))\n    Y_AXIS = np.array((0., 1., 0.))\n    Z_AXIS = np.array((0., 0., 1.))\n\n    # Useful abbreviations for diagonals\n    UL = UP + LEFT\n    UR = UP + RIGHT\n    DL = DOWN + LEFT\n    DR = DOWN + RIGHT\n\n    TOP = FRAME_Y_RADIUS * UP\n    BOTTOM = FRAME_Y_RADIUS * DOWN\n    LEFT_SIDE = FRAME_X_RADIUS * LEFT\n    RIGHT_SIDE = FRAME_X_RADIUS * RIGHT\n\nMathematical constant\n---------------------\n\n.. code-block:: python\n\n   PI = np.pi\n   TAU = 2 * PI\n   DEG = TAU / 360\n\nText\n----\n\n.. code-block:: python\n\n    NORMAL = \"NORMAL\"\n    ITALIC = \"ITALIC\"\n    OBLIQUE = \"OBLIQUE\"\n    BOLD = \"BOLD\"\n\nColours\n-------\n\nColor constants are determined based on the ``color`` configuration in default_config.yml or custom_config.yml\n\nHere are the preview of default colours. (Modified from \n`elteoremadebeethoven <https://elteoremadebeethoven.github.io/manim_3feb_docs.github.io/html/_static/colors/colors.html>`_)\n\n.. raw:: html\n\n    <div style=\"float: left;\">\n    <h3>BLUE</h3>\n    <div class=\"colors BLUE_E\"><p class=\"color-text\">BLUE_E</p></div>\n    <div class=\"colors BLUE_D\"><p class=\"color-text\">BLUE_D</p></div>\n    <div class=\"colors BLUE_C\"><p class=\"color-text\">BLUE_C</p></div>\n    <div class=\"colors BLUE_B\"><p class=\"color-text\">BLUE_B</p></div>\n    <div class=\"colors BLUE_A\"><p class=\"color-text\">BLUE_A</p></div>\n    </div>\n    <div style=\"float: left;\">\n    <h3>TEAL</h3>\n    <div class=\"colors TEAL_E\"><p class=\"color-text\">TEAL_E</p></div>\n    <div class=\"colors TEAL_D\"><p class=\"color-text\">TEAL_D</p></div>\n    <div class=\"colors TEAL_C\"><p class=\"color-text\">TEAL_C</p></div>\n    <div class=\"colors TEAL_B\"><p class=\"color-text\">TEAL_B</p></div>\n    <div class=\"colors TEAL_A\"><p class=\"color-text\">TEAL_A</p></div>\n    </div>\n    <div style=\"float: left;\">\n    <h3>GREEN</h3>\n    <div class=\"colors GREEN_E\"><p class=\"color-text\">GREEN_E</p></div>\n    <div class=\"colors GREEN_D\"><p class=\"color-text\">GREEN_D</p></div>\n    <div class=\"colors GREEN_C\"><p class=\"color-text\">GREEN_C</p></div>\n    <div class=\"colors GREEN_B\"><p class=\"color-text\">GREEN_B</p></div>\n    <div class=\"colors GREEN_A\"><p class=\"color-text\">GREEN_A</p></div>\n    </div>\n    <div style=\"float: left;\">\n    <h3>YELLOW</h3>\n    <div class=\"colors YELLOW_E\"><p class=\"color-text\">YELLOW_E</p></div>\n    <div class=\"colors YELLOW_D\"><p class=\"color-text\">YELLOW_D</p></div>\n    <div class=\"colors YELLOW_C\"><p class=\"color-text\">YELLOW_C</p></div>\n    <div class=\"colors YELLOW_B\"><p class=\"color-text\">YELLOW_B</p></div>\n    <div class=\"colors YELLOW_A\"><p class=\"color-text\">YELLOW_A</p></div>\n    </div>\n    <div style=\"float: left;\">\n    <h3>GOLD</h3>\n    <div class=\"colors GOLD_E\"><p class=\"color-text\">GOLD_E</p></div>\n    <div class=\"colors GOLD_D\"><p class=\"color-text\">GOLD_D</p></div>\n    <div class=\"colors GOLD_C\"><p class=\"color-text\">GOLD_C</p></div>\n    <div class=\"colors GOLD_B\"><p class=\"color-text\">GOLD_B</p></div>\n    <div class=\"colors GOLD_A\"><p class=\"color-text\">GOLD_A</p></div>\n    </div>\n    <div style=\"float: left;\">\n    <h3>RED</h3>\n    <div class=\"colors RED_E\"><p class=\"color-text\">RED_E</p></div>\n    <div class=\"colors RED_D\"><p class=\"color-text\">RED_D</p></div>\n    <div class=\"colors RED_C\"><p class=\"color-text\">RED_C</p></div>\n    <div class=\"colors RED_B\"><p class=\"color-text\">RED_B</p></div>\n    <div class=\"colors RED_A\"><p class=\"color-text\">RED_A</p></div>\n    </div>\n    <div style=\"float: left;\">\n    <h3>MAROON</h3>\n    <div class=\"colors MAROON_E\"><p class=\"color-text\">MAROON_E</p></div>\n    <div class=\"colors MAROON_D\"><p class=\"color-text\">MAROON_D</p></div>\n    <div class=\"colors MAROON_C\"><p class=\"color-text\">MAROON_C</p></div>\n    <div class=\"colors MAROON_B\"><p class=\"color-text\">MAROON_B</p></div>\n    <div class=\"colors MAROON_A\"><p class=\"color-text\">MAROON_A</p></div>\n    </div>\n    <div style=\"float: left;\">\n    <h3>PURPLE</h3>\n    <div class=\"colors PURPLE_E\"><p class=\"color-text\">PURPLE_E</p></div>\n    <div class=\"colors PURPLE_D\"><p class=\"color-text\">PURPLE_D</p></div>\n    <div class=\"colors PURPLE_C\"><p class=\"color-text\">PURPLE_C</p></div>\n    <div class=\"colors PURPLE_B\"><p class=\"color-text\">PURPLE_B</p></div>\n    <div class=\"colors PURPLE_A\"><p class=\"color-text\">PURPLE_A</p></div>\n    </div>\n    <div style=\"float: left;\">\n    <h3>GREY</h3>\n    <div class=\"colors GREY_E\"><p class=\"color-text\">GREY_E</p></div>\n    <div class=\"colors GREY_D\"><p class=\"color-text\">GREY_D</p></div>\n    <div class=\"colors GREY_C\"><p class=\"color-text\">GREY_C</p></div>\n    <div class=\"colors GREY_B\"><p class=\"color-text\">GREY_B</p></div>\n    <div class=\"colors GREY_A\"><p class=\"color-text\">GREY_A</p></div>\n    </div>\n    <div style=\"float: left;\">\n    <h3>Others</h3>\n    <div class=\"colors WHITE\"><p class=\"color-text\" style=\"color: BLACK\">WHITE</p></div>\n    <div class=\"colors BLACK\"><p class=\"color-text\">BLACK</p></div>\n    <div class=\"colors GREY_BROWN\"><p class=\"color-text-small\">GREY_BROWN</p></div>\n    <div class=\"colors DARK_BROWN\"><p class=\"color-text-small\">DARK_BROWN</p></div>\n    <div class=\"colors LIGHT_BROWN\"><p class=\"color-text-small\">LIGHT_BROWN</p></div>\n    <div class=\"colors PINK\"><p class=\"color-text\">PINK</p></div>\n    <div class=\"colors LIGHT_PINK\"><p class=\"color-text-small\">LIGHT_PINK</p></div>\n    <div class=\"colors GREEN_SCREEN\"><p class=\"color-text-small\">GREEN_SCREEN</p></div>\n    <div class=\"colors ORANGE\"><p class=\"color-text\">ORANGE</p></div>\n    </div>\n"
  },
  {
    "path": "docs/source/documentation/custom_config.rst",
    "content": "custom_config\n==============\n\n``directories``\n---------------\n\n- ``mirror_module_path``\n    (``True`` or ``False``) Whether to create a folder named the name of the \n    running file under the ``output`` path, and save the output (``images/`` \n    or ``videos/``) in it.\n\n- ``base``\n    The root directory that will hold files, such as video files manim renders,\n    or image resources that it pulls from\n\n- ``output``\n    Output file path, the videos will be saved in the ``videos/`` folder under it, \n    and the pictures will be saved in the ``images/`` folder under it.\n\n    For example, if you set ``output`` to ``\"/.../manim/output\"`` and \n    ``mirror_module_path`` to ``False``, then you exported ``Scene1`` in the code \n    file and saved the last frame, then the final directory structure will be like:\n\n    .. code-block:: text\n        :emphasize-lines: 9, 11\n\n            manim/\n            ├── manimlib/\n            │   ├── animation/\n            │   ├── ...\n            │   ├── default_config.yml\n            │   └── window.py\n            ├── output/\n            │   ├── images\n            │   │   └── Scene1.png\n            │   └── videos\n            │       └── Scene1.mp4\n            ├── code.py\n            └── custom_config.yml\n\n    But if you set ``mirror_module_path`` to ``True``, the directory structure will be:\n\n    .. code-block:: text\n        :emphasize-lines: 8\n\n            manim/\n            ├── manimlib/\n            │   ├── animation/\n            │   ├── ...\n            │   ├── default_config.yml\n            │   └── window.py\n            ├── output/\n            │   └── code/\n            │       ├── images\n            │       │   └── Scene1.png\n            │       └── videos\n            │           └── Scene1.mp4\n            ├── code.py\n            └── custom_config.yml\n\n- ``raster_images`` \n    The directory for storing raster images to be used in the code (including \n    ``.jpg``, ``.jpeg``, ``.png`` and ``.gif``), which will be read by ``ImageMobject``.\n\n- ``vector_images``\n    The directory for storing vector images to be used in the code (including \n    ``.svg`` and ``.xdv``), which will be read by ``SVGMobject``.\n\n- ``sounds``\n    The directory for storing sound files to be used in ``Scene.add_sound()`` (\n    including ``.wav`` and ``.mp3``).\n\n- ``cache``\n    The directory for storing temporarily generated cache files, including \n    ``Tex`` cache, ``Text`` cache and storage of object points.\n\n\n``window``\n----------\n\n- ``position_string``\n    The relative position of the playback window on the display (two characters, \n    the first character means upper(U) / middle(O) / lower(D), the second character \n    means left(L) / middle(O) / right(R)).\n\n- ``monitor_index``\n    If using multiple monitors, which one should the window show up in?\n\n- ``full_screen``\n    Should the preview window be full screen. If not, it defaults to half the screen\n\n- ``position``\n    This is an option to more manually set the default window position, in pixel\n    coordinates, e.g. (500, 300)\n\n- ``size``\n    Option to more manually set the default window size, in pixel coordinates,\n    e.g. (1920, 1080)\n\n\n``camera``\n----------\n\n- ``resolution``\n    Resolution to render at, e.g. (1920, 1080)\n\n- ``background_color``\n    Default background color of scenes\n\n- ``fps``\n    Framerate\n\n- ``background_opacity``\n    Opacity of the background\n\n\n``file_writer``\n---------------\nConfiguration specifying how files are written, e.g. what ffmpeg parameters to use\n\n\n``scene``\n-------\nSome default configuration for the Scene class\n\n\n``text``\n-------\n\n- ``font`` \n    Default font of Text\n\n- ``text_alignment``\n    Default text alignment for LaTeX\n\n``tex``\n-------\n\n- ``template``\n    Which configuration from the manimlib/tex_template.yml file should be used\n    to determine the latex compiler to use, and what preamble to include for \n    rendering tex. \n\n\n``sizes``\n---------\n\nValuess for various constants used in manimm to specify distances, like the height\nof the frame, the value of SMALL_BUFF, LARGE_BUFF, etc.\n\n\n``colors``\n----------\n\nColor pallete to use, determining values of color constants like RED, BLUE_E, TEAL, etc.\n\n``loglevel``\n------------\n\nCan be DEBUG / INFO / WARNING / ERROR / CRITICAL\n\n\n``universal_import_line``\n-------------------------\n\nImport line that need to execute when entering interactive mode directly.\n\n\n``ignore_manimlib_modules_on_reload``\n-------------------------------------\n\nWhen calling ``reload`` during the interactive mode, imported modules are\nby default reloaded, in case the user writing a scene which pulls from various\nother files they have written. By default, modules withinn the manim library will\nbe ignored, but one developing manim may want to set this to be False so that \nedits to the library are reloaded as well.\n"
  },
  {
    "path": "docs/source/documentation/mobject/index.rst",
    "content": "Mobject (TODO)\n=============="
  },
  {
    "path": "docs/source/documentation/scene/index.rst",
    "content": "Scene (TODO)\n============"
  },
  {
    "path": "docs/source/documentation/shaders/index.rst",
    "content": "Shaders (TODO)\n=============="
  },
  {
    "path": "docs/source/documentation/utils/index.rst",
    "content": "Utils (TODO)\n============"
  },
  {
    "path": "docs/source/getting_started/configuration.rst",
    "content": "CLI flags and configuration\n===========================\n\nCommand Line Interface\n----------------------\n\nTo run manim, you need to enter the directory at the same level as ``manimlib/`` \nand enter the command in the following format into terminal:\n\n.. code-block:: sh\n\n    manimgl <code>.py <Scene> <flags>\n    # or\n    manim-render <code>.py <Scene> <flags>\n\n- ``<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.\n- ``<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.\n- ``<flags>`` : CLI flags.\n\nSome useful flags\n^^^^^^^^^^^^^^^^^\n\n- ``-w`` to write the scene to a file.\n- ``-o`` to write the scene to a file and open the result.\n- ``-s`` to skip to the end and just show the final frame. \n\n  - ``-so`` will save the final frame to an image and show it.\n\n- ``-n <number>`` to skip ahead to the ``n``\\ ’th animation of a scene. \n- ``-f`` to make the playback window fullscreen.\n\nAll supported flags\n^^^^^^^^^^^^^^^^^^^\n\n========================================================== ====== =====================================================================================================================================================================================================\nflag                                                       abbr   function\n========================================================== ====== =====================================================================================================================================================================================================\n``--help``                                                 ``-h`` Show the help message and exit\n``--version``                                              ``-v`` Display the version of manimgl\n``--write_file``                                           ``-w`` Render the scene as a movie file\n``--skip_animations``                                      ``-s`` Skip to the last frame\n``--low_quality``                                          ``-l`` Render at a low quality (for faster rendering)\n``--medium_quality``                                       ``-m`` Render at a medium quality\n``--hd``                                                          Render at a 1080p quality\n``--uhd``                                                         Render at a 4k quality\n``--full_screen``                                          ``-f`` Show window in full screen\n``--presenter_mode``                                       ``-p`` Scene will stay paused during wait calls until space bar or right arrow is hit, like a slide show\n``--save_pngs``                                            ``-g`` Save each frame as a png\n``--gif``                                                  ``-i`` Save the video as gif\n``--transparent``                                          ``-t`` Render to a movie file with an alpha channel\n``--quiet``                                                ``-q``\n``--write_all``                                            ``-a`` Write all the scenes from a file\n``--open``                                                 ``-o`` Automatically open the saved file once its done\n``--finder``                                                      Show the output file in finder\n``--config``                                                      Guide for automatic configuration\n``--file_name FILE_NAME``                                         Name for the movie or image file\n``--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.\n``--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.\n``--resolution RESOLUTION``                                ``-r`` Resolution, passed as \"WxH\", e.g. \"1920x1080\"\n``--fps FPS``                                                     Frame rate, as an integer\n``--color COLOR``                                          ``-c`` Background color\n``--leave_progress_bars``                                         Leave progress bars displayed in terminal\n``--video_dir VIDEO_DIR``                                         Directory to write video\n``--config_file CONFIG_FILE``                                     Path to the custom configuration file\n``--log-level LOG_LEVEL``                                         Level of messages to Display, can be DEBUG / INFO / WARNING / ERROR / CRITICAL\n``--autoreload``                                                  Automatically reload Python modules to pick up code changes across during an interactive embedding\n========================================================== ====== =====================================================================================================================================================================================================\n\ncustom_config\n--------------\n\nIn order to perform more configuration (about directories, etc.) and permanently \nchange the default value (you don't have to add flags to the command every time), \nyou can modify ``custom_config.yml``. The meaning of each option is in \npage :doc:`../documentation/custom_config`.\n\nYou can also use different ``custom_config.yml`` for different directories, such as \nfollowing the directory structure:\n\n.. code-block:: text\n\n    manim/\n    ├── manimlib/\n    │   ├── animation/\n    │   ├── ...\n    │   ├── default_config.yml\n    │   └── window.py\n    ├── project/\n    │   ├── code.py\n    │   └── custom_config.yml\n    └── custom_config.yml\n\nWhen you enter the ``project/`` folder and run ``manimgl code.py <Scene>``, \nit will overwrite ``manim/default_config.yml`` with ``custom_config.yml`` \nin the ``project`` folder.\n\nAlternatively, you can use ``--config_file`` flag in CLI to specify configuration file manually.\n\n.. code-block:: sh\n\n    manimgl project/code.py --config_file /path/to/custom_config.yml"
  },
  {
    "path": "docs/source/getting_started/example_scenes.rst",
    "content": "Example Scenes\n==============\n\nAfter understanding the previous knowledge, we can understand more scenes.\nMany example scenes are given in ``example_scenes.py``, let's start with\nthe simplest and one by one.\n\nInteractiveDevlopment\n---------------------\n\n.. manim-example:: InteractiveDevelopment\n    :media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/InteractiveDevelopment.mp4\n\n    from manimlib import *\n\n    class InteractiveDevelopment(Scene):\n        def construct(self):\n            circle = Circle()\n            circle.set_fill(BLUE, opacity=0.5)\n            circle.set_stroke(BLUE_E, width=4)\n            square = Square()\n\n            self.play(ShowCreation(square))\n            self.wait()\n\n            # This opens an iPython terminal where you can keep writing\n            # lines as if they were part of this construct method.\n            # In particular, 'square', 'circle' and 'self' will all be\n            # part of the local namespace in that terminal.\n            self.embed()\n\n            # Try copying and pasting some of the lines below into\n            # the interactive shell\n            self.play(ReplacementTransform(square, circle))\n            self.wait()\n            self.play(circle.animate.stretch(4, 0))\n            self.play(Rotate(circle, 90 * DEG))\n            self.play(circle.animate.shift(2 * RIGHT).scale(0.25))\n\n            text = Text(\"\"\"\n                In general, using the interactive shell\n                is very helpful when developing new scenes\n            \"\"\")\n            self.play(Write(text))\n\n            # In the interactive shell, you can just type\n            # play, add, remove, clear, wait, save_state and restore,\n            # instead of self.play, self.add, self.remove, etc.\n\n            # To interact with the window, type touch().  You can then\n            # scroll in the window, or zoom by holding down 'z' while scrolling,\n            # and change camera perspective by holding down 'd' while moving\n            # the mouse.  Press 'r' to reset to the standard camera position.\n            # Press 'q' to stop interacting with the window and go back to\n            # typing new commands into the shell.\n\n            # In principle you can customize a scene to be responsive to\n            # mouse and keyboard interactions\n            always(circle.move_to, self.mouse_point)\n\nThis scene is similar to what we wrote in :doc:`quickstart`.\nAnd how to interact has been written in the comments.\nNo more explanation here.\n\nAnimatingMethods\n----------------\n\n.. manim-example:: AnimatingMethods\n    :media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/AnimatingMethods.mp4\n\n    class AnimatingMethods(Scene):\n        def construct(self):\n            grid = OldTex(r\"\\pi\").get_grid(10, 10, height=4)\n            self.add(grid)\n\n            # You can animate the application of mobject methods with the\n            # \".animate\" syntax:\n            self.play(grid.animate.shift(LEFT))\n\n            # Alternatively, you can use the older syntax by passing the\n            # method and then the arguments to the scene's \"play\" function:\n            self.play(grid.shift, LEFT)\n\n            # Both of those will interpolate between the mobject's initial\n            # state and whatever happens when you apply that method.\n            # For this example, calling grid.shift(LEFT) would shift the\n            # grid one unit to the left, but both of the previous calls to\n            # \"self.play\" animate that motion.\n\n            # The same applies for any method, including those setting colors.\n            self.play(grid.animate.set_color(YELLOW))\n            self.wait()\n            self.play(grid.animate.set_submobject_colors_by_gradient(BLUE, GREEN))\n            self.wait()\n            self.play(grid.animate.set_height(TAU - MED_SMALL_BUFF))\n            self.wait()\n\n            # The method Mobject.apply_complex_function lets you apply arbitrary\n            # complex functions, treating the points defining the mobject as\n            # complex numbers.\n            self.play(grid.animate.apply_complex_function(np.exp), run_time=5)\n            self.wait()\n\n            # Even more generally, you could apply Mobject.apply_function,\n            # which takes in functions form R^3 to R^3\n            self.play(\n                grid.animate.apply_function(\n                    lambda p: [\n                        p[0] + 0.5 * math.sin(p[1]),\n                        p[1] + 0.5 * math.sin(p[0]),\n                        p[2]\n                    ]\n                ),\n                run_time=5,\n            )\n            self.wait()\n\nThe new usage in this scene is ``.get_grid()`` and ``self.play(mob.animate.method(args))``.\n\n- ``.get_grid()`` method will return a new mobject containing multiple copies of this one arranged in a grid.\n- ``self.play(mob.animate.method(args))`` animates the method, and the details are in the comments above.\n\nTextExample\n-----------\n\n.. manim-example:: TextExample\n    :media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/TextExample.mp4\n\n    class TextExample(Scene):\n        def construct(self):\n            # To run this scene properly, you should have \"Consolas\" font in your computer\n            # for full usage, you can see https://github.com/3b1b/manim/pull/680\n            text = Text(\"Here is a text\", font=\"Consolas\", font_size=90)\n            difference = Text(\n                \"\"\"\n                The most important difference between Text and TexText is that\\n\n                you can change the font more easily, but can't use the LaTeX grammar\n                \"\"\",\n                font=\"Arial\", font_size=24,\n                # t2c is a dict that you can choose color for different text\n                t2c={\"Text\": BLUE, \"TexText\": BLUE, \"LaTeX\": ORANGE}\n            )\n            VGroup(text, difference).arrange(DOWN, buff=1)\n            self.play(Write(text))\n            self.play(FadeIn(difference, UP))\n            self.wait(3)\n\n            fonts = Text(\n                \"And you can also set the font according to different words\",\n                font=\"Arial\",\n                t2f={\"font\": \"Consolas\", \"words\": \"Consolas\"},\n                t2c={\"font\": BLUE, \"words\": GREEN}\n            )\n            fonts.set_width(FRAME_WIDTH - 1)\n            slant = Text(\n                \"And the same as slant and weight\",\n                font=\"Consolas\",\n                t2s={\"slant\": ITALIC},\n                t2w={\"weight\": BOLD},\n                t2c={\"slant\": ORANGE, \"weight\": RED}\n            )\n            VGroup(fonts, slant).arrange(DOWN, buff=0.8)\n            self.play(FadeOut(text), FadeOut(difference, shift=DOWN))\n            self.play(Write(fonts))\n            self.wait()\n            self.play(Write(slant))\n            self.wait()\n\nThe new classes in this scene are ``Text``, ``VGroup``, ``Write``, ``FadeIn`` and ``FadeOut``.\n\n- ``Text`` can create text, define fonts, etc. The usage ais clearly reflected in the above examples.\n- ``VGroup`` can put multiple ``VMobject`` together as a whole. In the example, the ``.arrange()`` method is called to arrange the sub-mobjects in sequence downward (``DOWN``), and the spacing is ``buff``.\n- ``Write`` is an animation that shows similar writing effects.\n- ``FadeIn`` fades the object in, the second parameter indicates the direction of the fade in.\n- ``FadeOut`` fades out the object, the second parameter indicates the direction of the fade out.\n\nTexTransformExample\n-------------------\n\n.. manim-example:: TexTransformExample\n    :media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/TexTransformExample.mp4\n\n    class TexTransformExample(Scene):\n        def construct(self):\n            to_isolate = [\"B\", \"C\", \"=\", \"(\", \")\"]\n            lines = VGroup(\n                # Passing in muliple arguments to Tex will result\n                # in the same expression as if those arguments had\n                # been joined together, except that the submobject\n                # hierarchy of the resulting mobject ensure that the\n                # Tex mobject has a subject corresponding to\n                # each of these strings.  For example, the Tex mobject\n                # below will have 5 subjects, corresponding to the\n                # expressions [A^2, +, B^2, =, C^2]\n                OldTex(\"A^2\", \"+\", \"B^2\", \"=\", \"C^2\"),\n                # Likewise here\n                OldTex(\"A^2\", \"=\", \"C^2\", \"-\", \"B^2\"),\n                # Alternatively, you can pass in the keyword argument\n                # \"isolate\" with a list of strings that should be out as\n                # their own submobject.  So the line below is equivalent\n                # to the commented out line below it.\n                OldTex(\"A^2 = (C + B)(C - B)\", isolate=[\"A^2\", *to_isolate]),\n                # OldTex(\"A^2\", \"=\", \"(\", \"C\", \"+\", \"B\", \")\", \"(\", \"C\", \"-\", \"B\", \")\"),\n                OldTex(\"A = \\\\sqrt{(C + B)(C - B)}\", isolate=[\"A\", *to_isolate])\n            )\n            lines.arrange(DOWN, buff=LARGE_BUFF)\n            for line in lines:\n                line.set_color_by_tex_to_color_map({\n                    \"A\": BLUE,\n                    \"B\": TEAL,\n                    \"C\": GREEN,\n                })\n\n            play_kw = {\"run_time\": 2}\n            self.add(lines[0])\n            # The animation TransformMatchingTex will line up parts\n            # of the source and target which have matching tex strings.\n            # Here, giving it a little path_arc makes each part sort of\n            # rotate into their final positions, which feels appropriate\n            # for the idea of rearranging an equation\n            self.play(\n                TransformMatchingTex(\n                    lines[0].copy(), lines[1],\n                    path_arc=90 * DEG,\n                ),\n                **play_kw\n            )\n            self.wait()\n\n            # Now, we could try this again on the next line...\n            self.play(\n                TransformMatchingTex(lines[1].copy(), lines[2]),\n                **play_kw\n            )\n            self.wait()\n            # ...and this looks nice enough, but since there's no tex\n            # in lines[2] which matches \"C^2\" or \"B^2\", those terms fade\n            # out to nothing while the C and B terms fade in from nothing.\n            # If, however, we want the C^2 to go to C, and B^2 to go to B,\n            # we can specify that with a key map.\n            self.play(FadeOut(lines[2]))\n            self.play(\n                TransformMatchingTex(\n                    lines[1].copy(), lines[2],\n                    key_map={\n                        \"C^2\": \"C\",\n                        \"B^2\": \"B\",\n                    }\n                ),\n                **play_kw\n            )\n            self.wait()\n\n            # And to finish off, a simple TransformMatchingShapes would work\n            # just fine.  But perhaps we want that exponent on A^2 to transform into\n            # the square root symbol.  At the moment, lines[2] treats the expression\n            # A^2 as a unit, so we might create a new version of the same line which\n            # separates out just the A.  This way, when TransformMatchingTex lines up\n            # all matching parts, the only mismatch will be between the \"^2\" from\n            # new_line2 and the \"\\sqrt\" from the final line.  By passing in,\n            # transform_mismatches=True, it will transform this \"^2\" part into\n            # the \"\\sqrt\" part.\n            new_line2 = OldTex(\"A^2 = (C + B)(C - B)\", isolate=[\"A\", *to_isolate])\n            new_line2.replace(lines[2])\n            new_line2.match_style(lines[2])\n\n            self.play(\n                TransformMatchingTex(\n                    new_line2, lines[3],\n                    transform_mismatches=True,\n                ),\n                **play_kw\n            )\n            self.wait(3)\n            self.play(FadeOut(lines, RIGHT))\n\n            # Alternatively, if you don't want to think about breaking up\n            # the tex strings deliberately, you can TransformMatchingShapes,\n            # which will try to line up all pieces of a source mobject with\n            # those of a target, regardless of the submobject hierarchy in\n            # each one, according to whether those pieces have the same\n            # shape (as best it can).\n            source = Text(\"the morse code\", height=1)\n            target = Text(\"here come dots\", height=1)\n\n            self.play(Write(source))\n            self.wait()\n            kw = {\"run_time\": 3, \"path_arc\": PI / 2}\n            self.play(TransformMatchingShapes(source, target, **kw))\n            self.wait()\n            self.play(TransformMatchingShapes(target, source, **kw))\n            self.wait()\n\nThe new classes in this scene are ``Tex``, ``TexText``, ``TransformMatchingTex``\nand ``TransformMatchingShapes``.\n\n- ``Tex`` uses LaTeX to create mathematical formulas.\n- ``TexText`` uses LaTeX to create text.\n- ``TransformMatchingTeX`` automatically transforms sub-objects according to the similarities and differences of tex in ``Tex``.\n- ``TransformMatchingShapes`` automatically transform sub-objects directly based on the similarities and differences of the object point sets.\n\nUpdatersExample\n---------------\n\n.. manim-example:: UpdatersExample\n    :media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/UpdatersExample.mp4\n\n    class UpdatersExample(Scene):\n        def construct(self):\n            square = Square()\n            square.set_fill(BLUE_E, 1)\n\n            # On all all frames, the constructor Brace(square, UP) will\n            # be called, and the mobject brace will set its data to match\n            # that of the newly constructed object\n            brace = always_redraw(Brace, square, UP)\n\n            text, number = label = VGroup(\n                Text(\"Width = \"),\n                DecimalNumber(\n                    0,\n                    show_ellipsis=True,\n                    num_decimal_places=2,\n                    include_sign=True,\n                )\n            )\n            label.arrange(RIGHT)\n\n            # This ensures that the method deicmal.next_to(square)\n            # is called on every frame\n            always(label.next_to, brace, UP)\n            # You could also write the following equivalent line\n            # label.add_updater(lambda m: m.next_to(brace, UP))\n\n            # If the argument itself might change, you can use f_always,\n            # for which the arguments following the initial Mobject method\n            # should be functions returning arguments to that method.\n            # The following line ensures that decimal.set_value(square.get_y())\n            # is called every frame\n            f_always(number.set_value, square.get_width)\n            # You could also write the following equivalent line\n            # number.add_updater(lambda m: m.set_value(square.get_width()))\n\n            self.add(square, brace, label)\n\n            # Notice that the brace and label track with the square\n            self.play(\n                square.animate.scale(2),\n                rate_func=there_and_back,\n                run_time=2,\n            )\n            self.wait()\n            self.play(\n                square.animate.set_width(5, stretch=True),\n                run_time=3,\n            )\n            self.wait()\n            self.play(\n                square.animate.set_width(2),\n                run_time=3\n            )\n            self.wait()\n\n            # In general, you can alway call Mobject.add_updater, and pass in\n            # a function that you want to be called on every frame.  The function\n            # should take in either one argument, the mobject, or two arguments,\n            # the mobject and the amount of time since the last frame.\n            now = self.time\n            w0 = square.get_width()\n            square.add_updater(\n                lambda m: m.set_width(w0 * math.cos(self.time - now))\n            )\n            self.wait(4 * PI)\n\nThe new classes and usage in this scene are ``always_redraw()``, ``DecimalNumber``, ``.to_edge()``,\n``.center()``, ``always()``, ``f_always()``, ``.set_y()`` and ``.add_updater()``.\n\n- ``always_redraw()`` function create a new mobject every frame.\n- ``DecimalNumber`` is a variable number, speed it up by breaking it into ``Text`` characters.\n- ``.to_edge()`` means to place the object on the edge of the screen.\n- ``.center()`` means to place the object in the center of the screen.\n- ``always(f, x)`` means that a certain function (``f(x)``) is executed every frame.\n- ``f_always(f, g)`` is similar to ``always``, executed ``f(g())`` every frame.\n- ``.set_y()`` means to set the ordinate of the object on the screen.\n- ``.add_updater()`` sets an update function for the object. For example: ``mob1.add_updater(lambda mob: mob.next_to(mob2))`` means ``mob1.next_to(mob2)`` is executed every frame.\n\nCoordinateSystemExample\n-----------------------\n\n.. manim-example:: CoordinateSystemExample\n    :media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/CoordinateSystemExample.mp4\n\n    class CoordinateSystemExample(Scene):\n        def construct(self):\n            axes = Axes(\n                # x-axis ranges from -1 to 10, with a default step size of 1\n                x_range=(-1, 10),\n                # y-axis ranges from -2 to 2 with a step size of 0.5\n                y_range=(-2, 2, 0.5),\n                # The axes will be stretched so as to match the specified\n                # height and width\n                height=6,\n                width=10,\n                # Axes is made of two NumberLine mobjects.  You can specify\n                # their configuration with axis_config\n                axis_config={\n                    \"stroke_color\": GREY_A,\n                    \"stroke_width\": 2,\n                },\n                # Alternatively, you can specify configuration for just one\n                # of them, like this.\n                y_axis_config={\n                    \"include_tip\": False,\n                }\n            )\n            # Keyword arguments of add_coordinate_labels can be used to\n            # configure the DecimalNumber mobjects which it creates and\n            # adds to the axes\n            axes.add_coordinate_labels(\n                font_size=20,\n                num_decimal_places=1,\n            )\n            self.add(axes)\n\n            # Axes descends from the CoordinateSystem class, meaning\n            # you can call call axes.coords_to_point, abbreviated to\n            # axes.c2p, to associate a set of coordinates with a point,\n            # like so:\n            dot = Dot(fill_color=RED)\n            dot.move_to(axes.c2p(0, 0))\n            self.play(FadeIn(dot, scale=0.5))\n            self.play(dot.animate.move_to(axes.c2p(3, 2)))\n            self.wait()\n            self.play(dot.animate.move_to(axes.c2p(5, 0.5)))\n            self.wait()\n\n            # Similarly, you can call axes.point_to_coords, or axes.p2c\n            # print(axes.p2c(dot.get_center()))\n\n            # We can draw lines from the axes to better mark the coordinates\n            # of a given point.\n            # Here, the always_redraw command means that on each new frame\n            # the lines will be redrawn\n            h_line = always_redraw(lambda: axes.get_h_line(dot.get_left()))\n            v_line = always_redraw(lambda: axes.get_v_line(dot.get_bottom()))\n\n            self.play(\n                ShowCreation(h_line),\n                ShowCreation(v_line),\n            )\n            self.play(dot.animate.move_to(axes.c2p(3, -2)))\n            self.wait()\n            self.play(dot.animate.move_to(axes.c2p(1, 1)))\n            self.wait()\n\n            # If we tie the dot to a particular set of coordinates, notice\n            # that as we move the axes around it respects the coordinate\n            # system defined by them.\n            f_always(dot.move_to, lambda: axes.c2p(1, 1))\n            self.play(\n                axes.animate.scale(0.75).to_corner(UL),\n                run_time=2,\n            )\n            self.wait()\n            self.play(FadeOut(VGroup(axes, dot, h_line, v_line)))\n\n            # Other coordinate systems you can play around with include\n            # ThreeDAxes, NumberPlane, and ComplexPlane.\n\n\nGraphExample\n------------\n\n.. manim-example:: GraphExample\n    :media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/GraphExample.mp4\n\n    class GraphExample(Scene):\n        def construct(self):\n            axes = Axes((-3, 10), (-1, 8))\n            axes.add_coordinate_labels()\n\n            self.play(Write(axes, lag_ratio=0.01, run_time=1))\n\n            # Axes.get_graph will return the graph of a function\n            sin_graph = axes.get_graph(\n                lambda x: 2 * math.sin(x),\n                color=BLUE,\n            )\n            # By default, it draws it so as to somewhat smoothly interpolate\n            # between sampled points (x, f(x)).  If the graph is meant to have\n            # a corner, though, you can set use_smoothing to False\n            relu_graph = axes.get_graph(\n                lambda x: max(x, 0),\n                use_smoothing=False,\n                color=YELLOW,\n            )\n            # For discontinuous functions, you can specify the point of\n            # discontinuity so that it does not try to draw over the gap.\n            step_graph = axes.get_graph(\n                lambda x: 2.0 if x > 3 else 1.0,\n                discontinuities=[3],\n                color=GREEN,\n            )\n\n            # Axes.get_graph_label takes in either a string or a mobject.\n            # If it's a string, it treats it as a LaTeX expression.  By default\n            # it places the label next to the graph near the right side, and\n            # has it match the color of the graph\n            sin_label = axes.get_graph_label(sin_graph, \"\\\\sin(x)\")\n            relu_label = axes.get_graph_label(relu_graph, Text(\"ReLU\"))\n            step_label = axes.get_graph_label(step_graph, Text(\"Step\"), x=4)\n\n            self.play(\n                ShowCreation(sin_graph),\n                FadeIn(sin_label, RIGHT),\n            )\n            self.wait(2)\n            self.play(\n                ReplacementTransform(sin_graph, relu_graph),\n                FadeTransform(sin_label, relu_label),\n            )\n            self.wait()\n            self.play(\n                ReplacementTransform(relu_graph, step_graph),\n                FadeTransform(relu_label, step_label),\n            )\n            self.wait()\n\n            parabola = axes.get_graph(lambda x: 0.25 * x**2)\n            parabola.set_stroke(BLUE)\n            self.play(\n                FadeOut(step_graph),\n                FadeOut(step_label),\n                ShowCreation(parabola)\n            )\n            self.wait()\n\n            # You can use axes.input_to_graph_point, abbreviated\n            # to axes.i2gp, to find a particular point on a graph\n            dot = Dot(fill_color=RED)\n            dot.move_to(axes.i2gp(2, parabola))\n            self.play(FadeIn(dot, scale=0.5))\n\n            # A value tracker lets us animate a parameter, usually\n            # with the intent of having other mobjects update based\n            # on the parameter\n            x_tracker = ValueTracker(2)\n            f_always(\n                dot.move_to,\n                lambda: axes.i2gp(x_tracker.get_value(), parabola)\n            )\n\n            self.play(x_tracker.animate.set_value(4), run_time=3)\n            self.play(x_tracker.animate.set_value(-2), run_time=3)\n            self.wait()\n\nSurfaceExample\n--------------\n\n.. manim-example:: SurfaceExample\n    :media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/SurfaceExample.mp4\n\n    class SurfaceExample(Scene):\n        CONFIG = {\n            \"camera_class\": ThreeDCamera,\n        }\n\n        def construct(self):\n            surface_text = Text(\"For 3d scenes, try using surfaces\")\n            surface_text.fix_in_frame()\n            surface_text.to_edge(UP)\n            self.add(surface_text)\n            self.wait(0.1)\n\n            torus1 = Torus(r1=1, r2=1)\n            torus2 = Torus(r1=3, r2=1)\n            sphere = Sphere(radius=3, resolution=torus1.resolution)\n            # You can texture a surface with up to two images, which will\n            # be interpreted as the side towards the light, and away from\n            # the light.  These can be either urls, or paths to a local file\n            # in whatever you've set as the image directory in\n            # the custom_config.yml file\n\n            # day_texture = \"EarthTextureMap\"\n            # night_texture = \"NightEarthTextureMap\"\n            day_texture = \"https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Whole_world_-_land_and_oceans.jpg/1280px-Whole_world_-_land_and_oceans.jpg\"\n            night_texture = \"https://upload.wikimedia.org/wikipedia/commons/thumb/b/ba/The_earth_at_night.jpg/1280px-The_earth_at_night.jpg\"\n\n            surfaces = [\n                TexturedSurface(surface, day_texture, night_texture)\n                for surface in [sphere, torus1, torus2]\n            ]\n\n            for mob in surfaces:\n                mob.shift(IN)\n                mob.mesh = SurfaceMesh(mob)\n                mob.mesh.set_stroke(BLUE, 1, opacity=0.5)\n\n            # Set perspective\n            frame = self.camera.frame\n            frame.set_euler_angles(\n                theta=-30 * DEG,\n                phi=70 * DEG,\n            )\n\n            surface = surfaces[0]\n\n            self.play(\n                FadeIn(surface),\n                ShowCreation(surface.mesh, lag_ratio=0.01, run_time=3),\n            )\n            for mob in surfaces:\n                mob.add(mob.mesh)\n            surface.save_state()\n            self.play(Rotate(surface, PI / 2), run_time=2)\n            for mob in surfaces[1:]:\n                mob.rotate(PI / 2)\n\n            self.play(\n                Transform(surface, surfaces[1]),\n                run_time=3\n            )\n\n            self.play(\n                Transform(surface, surfaces[2]),\n                # Move camera frame during the transition\n                frame.animate.increment_phi(-10 * DEG),\n                frame.animate.increment_theta(-20 * DEG),\n                run_time=3\n            )\n            # Add ambient rotation\n            frame.add_updater(lambda m, dt: m.increment_theta(-0.1 * dt))\n\n            # Play around with where the light is\n            light_text = Text(\"You can move around the light source\")\n            light_text.move_to(surface_text)\n            light_text.fix_in_frame()\n\n            self.play(FadeTransform(surface_text, light_text))\n            light = self.camera.light_source\n            self.add(light)\n            light.save_state()\n            self.play(light.animate.move_to(3 * IN), run_time=5)\n            self.play(light.animate.shift(10 * OUT), run_time=5)\n\n            drag_text = Text(\"Try moving the mouse while pressing d or s\")\n            drag_text.move_to(light_text)\n            drag_text.fix_in_frame()\n\n            self.play(FadeTransform(light_text, drag_text))\n            self.wait()\n\nThis scene shows an example of using a three-dimensional surface, and\nthe related usage has been briefly described in the notes.\n\n- ``.fix_in_frame()`` makes the object not change with the view angle of the screen, and is always displayed at a fixed position on the screen.\n\nOpeningManimExample\n-------------------\n\n.. manim-example:: OpeningManimExample\n    :media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/OpeningManimExample.mp4\n\n\n    class OpeningManimExample(Scene):\n        def construct(self):\n            intro_words = Text(\"\"\"\n                The original motivation for manim was to\n                better illustrate mathematical functions\n                as transformations.\n            \"\"\")\n            intro_words.to_edge(UP)\n\n            self.play(Write(intro_words))\n            self.wait(2)\n\n            # Linear transform\n            grid = NumberPlane((-10, 10), (-5, 5))\n            matrix = [[1, 1], [0, 1]]\n            linear_transform_words = VGroup(\n                Text(\"This is what the matrix\"),\n                IntegerMatrix(matrix, include_background_rectangle=True),\n                Text(\"looks like\")\n            )\n            linear_transform_words.arrange(RIGHT)\n            linear_transform_words.to_edge(UP)\n            linear_transform_words.set_stroke(BLACK, 10, background=True)\n\n            self.play(\n                ShowCreation(grid),\n                FadeTransform(intro_words, linear_transform_words)\n            )\n            self.wait()\n            self.play(grid.animate.apply_matrix(matrix), run_time=3)\n            self.wait()\n\n            # Complex map\n            c_grid = ComplexPlane()\n            moving_c_grid = c_grid.copy()\n            moving_c_grid.prepare_for_nonlinear_transform()\n            c_grid.set_stroke(BLUE_E, 1)\n            c_grid.add_coordinate_labels(font_size=24)\n            complex_map_words = TexText(\"\"\"\n                Or thinking of the plane as $\\\\mathds{C}$,\\\\\\\\\n                this is the map $z \\\\rightarrow z^2$\n            \"\"\")\n            complex_map_words.to_corner(UR)\n            complex_map_words.set_stroke(BLACK, 5, background=True)\n\n            self.play(\n                FadeOut(grid),\n                Write(c_grid, run_time=3),\n                FadeIn(moving_c_grid),\n                FadeTransform(linear_transform_words, complex_map_words),\n            )\n            self.wait()\n            self.play(\n                moving_c_grid.animate.apply_complex_function(lambda z: z**2),\n                run_time=6,\n            )\n            self.wait(2)\n\nThis scene is a comprehensive application of a two-dimensional scene.\n\nAfter seeing these scenes, you have already understood part of the\nusage of manim. For more examples, see `the video code of 3b1b <https://github.com/3b1b/videos>`_.\n"
  },
  {
    "path": "docs/source/getting_started/installation.rst",
    "content": "Installation\n============\n\nManim runs on Python 3.7 or higher.\n\nSystem requirements are：\n\n- `FFmpeg <https://ffmpeg.org/>`__\n- `OpenGL <https://www.opengl.org//>`__ (included in python package ``PyOpenGL``)\n- `LaTeX <https://www.latex-project.org>`__ (optional, if you want to use LaTeX)\n- `Pango <https://pango.org>`__ (only for Linux)\n\n\nInstall FFmpeg\n--------------\n\n\n\nInstall FFmpeg Windows\n------------------------\n.. code-block:: cmd\n\n   choco install ffmpeg\n\n\n# Install FFmepeg Linux\n----------------------------\n.. code-block:: sh\n\n   $ sudo apt update\n   $ sudo apt install ffmpeg\n   $ ffmpeg -version\n  \n# Install FFmpeg MacOS\n----------------------------\n- 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)\n\n\n\nDirectly\n--------\n\n.. code-block:: sh\n\n   # Install manimgl\n   pip install manimgl\n\n   # Try it out\n   manimgl\n\nIf you want to hack on manimlib itself, clone this repository and in\nthat directory execute:\n\n.. code-block:: sh\n\n   # Install python requirements\n   pip install -e .\n\n   # Try it out\n   manimgl example_scenes.py OpeningManimExample\n   # or\n   manim-render example_scenes.py OpeningManimExample\n\nIf you run the above command and no error message appears, \nthen you have successfully installed all the environments required by manim.\n\nDirectly (Windows)\n------------------\n\n1. `Install\n   FFmpeg <https://www.wikihow.com/Install-FFmpeg-on-Windows>`__, and make sure that its path is in the PATH environment variable.\n2. Install a LaTeX distribution.\n   `TeXLive-full <http://tug.org/texlive/>`__ is recommended.\n3. Install the remaining Python packages.\n\n.. code-block:: sh  \n\n   git clone https://github.com/3b1b/manim.git\n   cd manim  \n   pip install -e . \n   manimgl example_scenes.py OpeningManimExample\n\nFor Anaconda\n------------\n\n-  Install FFmpeg and LaTeX as above.\n-  Create a conda environment using\n\n.. code-block:: sh\n   \n   git clone https://github.com/3b1b/manim.git\n   cd manim \n   conda create -n manim python=3.8\n   conda activate manim\n   pip install -e .\n"
  },
  {
    "path": "docs/source/getting_started/quickstart.rst",
    "content": "Quick Start\n===========\n\nAfter installing the manim environment according to the instructions on the\n:doc:`installation` page, you can try to make a scene yourself from scratch.\n\nFirst, create a new ``.py`` file (such as ``start.py``) according to the following\ndirectory structure:\n\n.. code-block:: text\n    :emphasize-lines: 8\n\n    manim/\n    ├── manimlib/\n    │   ├── animation/\n    │   ├── ...\n    │   ├── default_config.yml\n    │   └── window.py\n    ├── custom_config.yml\n    └── start.py\n\nAnd paste the following code (I will explain the function of each line in detail later):\n\n.. code-block:: python\n    :linenos:\n\n    from manimlib import *\n\n    class SquareToCircle(Scene):\n        def construct(self):\n            circle = Circle()\n            circle.set_fill(BLUE, opacity=0.5)\n            circle.set_stroke(BLUE_E, width=4)\n\n            self.add(circle)\n\nAnd run this command:\n\n.. code-block:: sh\n\n    manimgl start.py SquareToCircle\n\nA window will pop up on the screen. And then you can :\n\n- scroll the middle mouse button to move the screen up and down\n- hold down the :kbd:`z` on the keyboard while scrolling the middle mouse button to zoom the screen\n- hold down the :kbd:`s` key on the keyboard and move the mouse to pan the screen\n- hold down the :kbd:`d` key on the keyboard and move the mouse to change the three-dimensional perspective.\n\nFinally, you can close the window and exit the program by pressing :kbd:`q`.\n\nRun this command again:\n\n.. code-block:: sh\n\n    manimgl start.py SquareToCircle -os\n\nAt this time, no window will pop up. When the program is finished, this rendered\nimage will be automatically opened (saved in the subdirectory ``images/`` of the same\nlevel directory of ``start.py`` by default):\n\n.. image:: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/quickstart/SquareToCircle.png\n    :align: center\n\nMake an image\n-------------\n\nNext, let's take a detailed look at what each row does.\n\n**Line 1**:\n\n.. code-block:: python\n    \n    from manimlib import *\n    \nThis will import all the classes that may be used when using manim.\n\n**Line 3**:\n\n.. code-block:: python\n\n    class SquareToCircle(Scene):\n\nCreate a :class:`Scene` subclass ``SquareToCircle``, which will be\nthe scene you write and render.\n\n**Line 4**:\n\n.. code-block:: python\n\n    def construct(self):\n\nWrite the ``construct()`` method, the content of which will determine\nhow to create the mobjects in the screen and what operations need to be performed.\n\n**Line 5**:\n\n.. code-block:: python\n\n    circle = Circle()\n\nCreate a circle (an instance of the :class:`Circle` class), called ``circle``\n\n**Line 6~7**:\n\n.. code-block:: python\n\n    circle.set_fill(BLUE, opacity=0.5)\n    circle.set_stroke(BLUE_E, width=4)\n\nSet the circle style by calling the circle's method.\n\n- The ``.set_fill()`` method sets the fill color of this circle to blue (``BLUE``, defined in :doc:`../documentation/constants`), and the fill transparency to 0.5.\n- The ``.set_stroke()`` method sets the stroke color of this circle to dark blue (``BLUE_E``, defined in :doc:`../documentation/constants`), and the stroke width to 4.\n\n**Line 9**:\n\n.. code-block:: python\n\n    self.add(circle)\n\nAdd this circle to the screen through the ``.add()`` method of :class:`Scene`.\n\nAdd animations\n--------------\n\nLet's change some codes and add some animations to make videos instead of just pictures.\n\n.. code-block:: python\n    :linenos:\n\n    from manimlib import *\n\n    class SquareToCircle(Scene):\n        def construct(self):\n            circle = Circle()\n            circle.set_fill(BLUE, opacity=0.5)\n            circle.set_stroke(BLUE_E, width=4)\n            square = Square()\n\n            self.play(ShowCreation(square))\n            self.wait()\n            self.play(ReplacementTransform(square, circle))\n            self.wait()\n\nRun this command this time:\n\n.. code-block:: sh\n\n    manimgl start.py SquareToCircle\n\nThe pop-up window will play animations of drawing a square and transforming\nit into a circle. If you want to save this video, run:\n\n.. code-block:: sh\n    \n    manimgl start.py SquareToCircle -o\n\nThis time there will be no pop-up window, but the video file (saved in the subdirectory\n``videos/`` of the same level directory of ``start.py`` by default) will be automatically\nopened after the operation is over:\n\n.. raw:: html\n\n    <video class=\"manim-video\" controls loop autoplay src=\"https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/quickstart/SquareToCircle.mp4\"></video>\n\nLet's take a look at the code this time. The first 7 lines are the same as the previous\nones, and the 8th line is similar to the 5th line, which creates an instance of the\n:class:`Square` class and named it ``square``.\n\n**Line 10**:\n\n.. code-block:: python\n\n    self.play(ShowCreation(square))\n\nAn animation is played through :class:`Scene`'s ``.play()`` method. :class:`ShowCreation`\nis an animation that shows the process of creating a given mobject.\n``self.play(ShowCreation(square))`` is to play the animation of creating ``square``.\n\n**Line 11**:\n\n.. code-block:: python\n\n    self.wait()\n\nUse :class:`Scene`'s ``.wait()`` method to pause (default 1s), you can pass in\nparameters to indicate the pause time (for example, ``self.wait(3)`` means pause for 3s).\n\n**Line 12**:\n\n.. code-block:: python\n\n    self.play(ReplacementTransform(square, circle))\n\nPlay the animation that transforms ``square`` into ``circle``.\n``ReplacementTransform(A, B)`` means to transform A into B's pattern and replace A with B.\n\n**Line 13**: Same as line 11, pause for 1s.\n\n\nEnable interaction\n------------------\n\nInteraction is a new feature of the new version. You can add the following line\nat the end of the code to enable interaction:\n\n.. code-block:: python\n\n    self.embed()\n\nThen run ``manimgl start.py SquareToCircle``. \n\nAfter the previous animation is executed, the ipython terminal will be opened on\nthe command line. After that, you can continue to write code in it, and the statement\nyou entered will be executed immediately after pressing :kbd:`Enter`.\n\nFor example: input the following lines (without comment lines) into it respectively\n(``self.play`` can be abbreviated as ``play`` in this mode):\n\n.. code-block:: python\n\n    # Stretched 4 times in the vertical direction\n    play(circle.animate.stretch(4, dim=0))\n    # Rotate the ellipse 90°\n    play(Rotate(circle, TAU / 4))\n    # Move 2 units to the right and shrink to 1/4 of the original\n    play(circle.animate.shift(2 * RIGHT), circle.animate.scale(0.25))\n    # Insert 10 curves into circle for non-linear transformation (no animation will play)\n    circle.insert_n_curves(10)\n    # Apply a complex transformation of f(z)=z^2 to all points on the circle\n    play(circle.animate.apply_complex_function(lambda z: z**2))\n    # Close the window and exit the program\n    exit()\n\nYou will get an animation similar to the following:\n\n.. raw:: html\n\n    <video class=\"manim-video\" controls loop autoplay src=\"https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/quickstart/SquareToCircleEmbed.mp4\"></video>\n\nIf you want to enter the interactive mode directly, you don't have to write an\nempty scene containing only ``self.embed()``, you can directly run the following command\n(this will enter the ipython terminal while the window pops up):\n\n.. code-block:: sh\n\n    manimgl\n\nYou succeeded!\n--------------\n\nAfter reading the above content, you already know how to use manim.\nBelow you can see some examples, in the :doc:`example_scenes` page.\nBut before that, you'd better have a look at the :doc:`configuration` of manim.\n\n"
  },
  {
    "path": "docs/source/getting_started/structure.rst",
    "content": "Manim's structure\n=================\n\n\nManim's directory structure\n---------------------------\n\nThe manim directory looks very complicated, with a lot of files, \nbut the structure is clear.\n\nBelow is the directory structure of manim:\n\n.. code-block:: text\n\n    manimlib/ # manim library\n    ├── __init__.py          \n    ├── __main__.py          \n    ├── default_config.yml   # Default configuration file\n    ├── config.py            # Process CLI flags\n    ├── constants.py         # Defined some constants\n    ├── extract_scene.py     # Extract and run the scene\n    ├── shader_wrapper.py    # Shaders' Wrapper for convenient control\n    ├── window.py            # Playback window\n    ├── tex_templates/ # Templates preset for LaTeX\n    │   ├── tex_templates.tex   # Tex template (will be compiled with latex, default)\n    │   └── ctex_templates.tex  # Tex template that support Chinese (will be compiled with xelatex)\n    ├── camera/\n    │   └── camera.py        # Including Camera and CameraFrame\n    ├── scene/\n    │   ├── scene_file_writer.py     # Used to write scene to video file\n    │   ├── scene.py                 # The basic Scene class\n    │   ├── three_d_scene.py         # Three-dimensional scene\n    │   ├── sample_space_scene.py    # Probability related sample space scene\n    │   └── vector_space_scene.py    # Vector field scene\n    ├── animation/\n    │   ├── animation.py     # The basic class of animation\n    │   ├── composition.py   # Animation group\n    │   ├── creation.py      # Animation related to Create\n    │   ├── fading.py        # Fade related animation\n    │   ├── growing.py       # Animation related to Grow\n    │   ├── indication.py    # Some animations for emphasis\n    │   ├── movement.py      # Animation related to movement\n    │   ├── numbers.py       # Realize changes to DecimalNumber\n    │   ├── rotation.py      # Animation related to rotation\n    │   ├── specialized.py   # Some uncommon animations for special projects\n    │   ├── transform_matching_parts.py # Transform which can automatically match parts\n    │   ├── transform.py     # Some Transforms\n    │   └── update.py        # Realize update from function\n    ├── mobject/\n    │   ├── mobject.py       # The basic class of all math object\n    │   ├── types/ # 4 types of mobject\n    │   │   ├── dot_cloud.py            # Dot cloud (an subclass of PMobject)\n    │   │   ├── image_mobject.py        # Insert pictures\n    │   │   ├── point_cloud_mobject.py  # PMobject (mobject composed of points)\n    │   │   ├── surface.py              # ParametricSurface\n    │   │   └── vectorized_mobject.py   # VMobject (vectorized mobject)\n    │   ├── svg/ # mobject related to svg\n    │   │   ├── svg_mobject.py          # SVGMobject\n    │   │   ├── brace.py                # Brace\n    │   │   ├── drawings.py             # Some special mobject of svg image\n    │   │   ├── tex_mobject.py          # Tex and TexText implemented by LaTeX\n    │   │   └── text_mobject.py         # Text implemented by manimpango\n    │   ├── changing.py             # Dynamically changing mobject\n    │   ├── coordinate_systems.py   # coordinate system\n    │   ├── frame.py                # mobject related to frame\n    │   ├── functions.py            # ParametricFunction\n    │   ├── geometry.py             # geometry mobjects\n    │   ├── matrix.py               # matrix\n    │   ├── mobject_update_utils.py # some defined updater\n    │   ├── number_line.py          # Number line\n    │   ├── numbers.py              # Numbers that can be changed\n    │   ├── probability.py          # mobject related to probability\n    │   ├── shape_matchers.py       # mobject adapted to the size of other objects\n    │   ├── three_dimensions.py     # Three-dimensional objects\n    │   ├── value_tracker.py        # ValueTracker which storage number\n    │   └── vector_field.py         # VectorField\n    ├── once_useful_constructs/  # 3b1b's Common scenes written for some videos\n    │   └── ...\n    ├── shaders/ # GLSL scripts for rendering\n    │   ├── simple_vert.glsl    # a simple glsl script for position\n    │   ├── insert/ # glsl scripts to be inserted in other glsl scripts\n    │   │   ├── NOTE.md   # explain how to insert glsl scripts\n    │   │   └── ...       # useful scripts\n    │   ├── image/ # glsl for images\n    │   │   └── ... # containing shaders for vertex and fragment\n    │   ├── quadratic_bezier_fill/ # glsl for the fill of quadratic bezier curve\n    │   │   └── ... # containing shaders for vertex, fragment and geometry\n    │   ├── quadratic_bezier_stroke/ # glsl for the stroke of quadratic bezier curve\n    │   │   └── ... # containing shaders for vertex, fragment and geometry\n    │   ├── surface/ # glsl for surfaces\n    │   │   └── ... # containing shaders for vertex and fragment\n    │   ├── textured_surface/ # glsl for textured_surface\n    │   │   └── ... # containing shaders for vertex and fragment\n    │   └── true_dot/ # glsl for a dot\n    │       └── ... # containing shaders for vertex, fragment and geometry\n    └── utils/ # Some useful utility functions\n        ├── bezier.py             # For bezier curve\n        ├── color.py              # For color\n        ├── dict_ops.py           # Functions related to dictionary processing\n        ├── customization.py      # Read from custom_config.yml\n        ├── debug.py              # Utilities for debugging in program\n        ├── directories.py        # Read directories from config file\n        ├── family_ops.py         # Process family members\n        ├── file_ops.py           # Process files and directories\n        ├── images.py             # Read image\n        ├── iterables.py          # Functions related to list/dictionary processing\n        ├── paths.py              # Curve path\n        ├── rate_functions.py     # Some defined rate_functions\n        ├── simple_functions.py   # Some commonly used functions\n        ├── sounds.py             # Process sounds\n        ├── space_ops.py          # Space coordinate calculation\n        ├── strings.py            # Process strings\n        └── tex_file_writing.py   # Use LaTeX to write strings as svg\n\nInheritance structure of manim's classes\n----------------------------------------\n\n`Here <https://github.com/3b1b/manim/files/5824383/manim_shaders_structure.pdf>`_ \nis a pdf showed inheritance structure of manim's classes, large, \nbut basically all classes have included:\n\n.. image:: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/manim_shaders_structure.png\n\nManim execution process\n-----------------------\n\n.. image:: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/manim_shaders_process_en.png\n"
  },
  {
    "path": "docs/source/getting_started/whatsnew.rst",
    "content": "What's new\n==========\n\nUsage changes of new version manim\n----------------------------------\n\nThere are many changes in the new version of manim, and here are only the changes that \nmay have an impact at the code writing level. \n\nSome of the changes here may not have any major impact on the use, and some changes \nthat affect the use are not mentioned below.\n\nThis document is for reference only, see the source code for details.\n\n- ``Animation``\n  \n  - Added ``Fade`` as the parent class of ``FadeIn`` and ``FadeOut``\n  - ``FadeIn`` and ``FadeOut`` can be passed in ``shift`` and ``scale`` parameters\n  - Deleted ``FadeInFrom, FadeInFromDown, FadeOutAndShift, FadeOutAndShiftDown, FadeInFromLarge``, these can be used ``FadeIn, FadeOut`` to achieve the same effect more easily\n  - Added ``FadeTransform`` to cross fade between two objects, and subclass ``FadeTransformPieces``\n  - Added ``CountInFrom(decimal_mob, source_number=0)`` to count ``decimal_mob`` from ``source_number`` to the current value\n  - ``Rotating`` can directly pass in ``angle`` and ``axis`` without writing keywords ``angle=, axis=``\n  - ``Rotate`` has become a subclass of ``Rotating``, and the distortion effect in ``Transform`` will not appear\n  - Removed ``MoveCar`` animation\n  - Added ``TransformMatchingShapes(mobject, target_mobject)`` and ``TransformMatchingTex(mobject, target_mobject)``\n  \n- ``Camera``\n\n  - Removed all camera classes except ``Camera`` (``MappingCamera``, ``MovingCamera``, ``MultiCamera``) and all functions in ``ThreeDCamera``\n  - Implemented ``CameraFrame`` (as a ``Mobject``)\n  \n    - Can be called by ``self.camera.frame`` in ``Scene``\n    - All methods of ``Mobject`` can be used, such as ``.shift()``, ``.scale()``, etc.\n    - Call ``.to_default_state()`` to place in the default position\n    - Set the Euler angles of the camera by ``.set_euler_angles(theta, phi, gamma)``\n    - Set three single Euler angles by ``.set_theta(theta)``, ``.set_phi(phi)``, ``.set_gamma(gamma)``\n    - Use ``.increment_theta(dtheta)``, ``.increment_phi(dphi)``, ``.increment_gamma(gamma)`` to increase the three Euler angles by a certain value. Can be used to realize automatic rotation ``self.camera.frame.add_updater(lambda mob, dt: mob.increment_theta(0.1 * dt))``\n  \n  - ``Camera`` adds a light source, which is a ``Point``, which can be called by ``self.camera.light_source`` in ``Scene`` to move and so on. The default position is ``(- 10, 10, 10)``\n  \n- Delete ``Container``\n- ``Mobject``\n  \n  - ``svg`` related\n  \n    - Added ``Checkmark`` and ``Exmark``\n    - Some unnecessary classes have been removed from ``drawings.py``\n    - Removed ``Code`` and ``Paragraph`` (by mistake)\n    - ``TexMobject`` is renamed to ``Tex``, ``TextMobject`` is renamed to ``TexText``\n    - ``font_size`` has been added to ``Tex``, ``TexText`` and ``Text``\n    - ``Tex`` and ``TexText`` added ``isolate``, which is a list, which will be automatically split\n  \n  - Mobject ``types``\n  \n    - Added a new class ``Surface``, which is the parent class of ``ParametricSurface`` and ``TexturedSurface``.\n    - Added the group ``SGroup`` for ``Surface``\n    - Added ``TexturedSurface(uv_surface, image_file, dark_image_file=None)``, where ``uv_surface`` is a ``Surface``, ``image_file`` is the image to be posted, and ``dark_image_file`` is the image to be posted in the dark (default and ``image_file`` is the same)\n    - Deleted ``Mobject1D``, ``Mobject2D``, ``PointCloudDot``\n    - Added ``DotCloud`` (a ``PMobject``), which has been greatly optimized\n    - Removed ``AbstractImageMobject``, ``ImageMobjectFromCamera``\n    - Removed ``sheen`` from ``VMobject``\n  \n  - ``Mobject``\n  \n    - Added ``gloss`` and ``shadow``, which are the numbers between ``[0, 1]`` respectively. There are four methods of ``.get_gloss()``, ``.set_gloss(gloss)``, ``.get_shadow()``, ``.set_shadow(shadow)``\n    - Added ``.get_grid(n_rows, n_cols)`` to copy into grid\n    - Added ``.set_color_by_code(glsl_code)`` to use GLSL code to change the color\n    - Added ``.set_color_by_xyz_func(glsl_snippet, min_value=-5.0, max_value=5.0, colormap=\"viridis\")`` to pass in GLSL expression in the form of ``x,y,z``, the return value should be a floating point number\n  \n  - Coordinate system (including ``Axes``, ``ThreeDAxes``, ``NumberPlane``, ``ComplexPlane``)\n\n    - No longer use ``x_min``, ``x_max``, ``y_min``, ``y_max``, but use ``x_range``, ``y_range`` as a ``np.array()``, containing three numbers ``np.array([ Minimum, maximum, step size])``\n    - Added the abbreviation ``.i2gp(x, graph)`` of ``.input_to_graph_point(x, graph)``\n    - Added some functions of the original ``GraphScene``\n  \n      - Added ``.get_v_line(point)``, ``.get_h_line(point)`` to return the line from ``point`` to the two coordinate axes, and specify the line type through the keyword argument of ``line_func`` (default ``DashedLine``)\n      - Added ``.get_graph_label(graph, label, x, direction, buff, color)`` to return the label added to the image\n      - Added ``.get_v_line_to_graph(x, graph)``, ``.get_h_line_to_graph(x, graph)`` to return the line from the point with the abscissa of ``x`` on the ``graph`` to the two- axis line\n      - Added ``.angle_of_tangent(x, graph, dx=EPSILON)``, returns the inclination angle of ``graph`` at ``x``\n      - Added ``.slope_of_tangent(x, graph, dx=EPSILON)``, returns the slope of tangent line of ``graph`` at ``x``\n      - Added ``.get_tangent_line(x, graph, length=5)`` to return the tangent line of ``graph`` at ``x``\n      - Added ``.get_riemann_rectangles(graph, x_range, dx, input_sample_type, ...)`` to return Riemann rectangles (a ``VGroup``)\n  \n    - The attribute ``number_line_config`` of ``Axes`` is renamed to ``axis_config``\n    - ``Axes`` original ``.get_coordinate_labels(x_values, y_values)`` method was renamed to ``.add_coordinate_labels(x_values, y_values)`` (but it is not added to the screen)\n    - ``.add_coordinate_labels(numbers)`` of ``ComplexPlane`` will directly add the coordinates to the screen\n  \n  - ``NumberLine``\n  \n    - No longer use ``x_min``, ``x_max``, ``tick_frequency``, but use ``x_range``, which is an array containing three numbers ``[min, max, step]``\n    - The original ``label_direction`` attribute changed to the ``line_to_number_direction`` attribute\n    - Replace ``tip_width`` and ``tip_height`` with ``tip_config`` (dictionary) attributes\n    - The original ``exclude_zero_from_default`` attribute is modified to the ``numbers_to_exclude`` attribute (default is None)\n    - The original ``.add_tick_marks()`` method was changed to the ``.add_ticks()`` method\n    - Delete the ``.get_number_mobjects(*numbers)`` method, only use the ``.add_numbers(x_values=None, excluding=None)`` method\n  \n  - Three-dimensional objects\n  \n    - Added ``SurfaceMesh(uv_surface)``, pass in a ``Surface`` to generate its uv mesh\n    - ``ParametricSurface`` no longer uses ``u_min, u_max, v_min, v_max``, but instead uses ``u_range, v_range``, which is a tuple (``(min, max)``), and ``resolution`` can be set larger, don’t worry Speed ​​issue\n    - Added ``Torus``, controlled by ``r1, r2`` keyword parameters\n    - Added ``Cylinder``, controlled by ``height, radius`` keyword parameters\n    - Added ``Line3D`` (extremely thin cylinder), controlled by the ``width`` keyword parameter\n    - Added ``Disk3D``, controlled by ``radius`` keyword parameter\n    - Add ``Square3D``, controlled by ``side_length`` keyword parameter\n    - Improved ``Cube`` and ``Prism``, the usage remains unchanged\n  \n  - Other objects\n  \n    - ``ParametricFunction`` is renamed to ``ParametricCurve``. Instead of using ``t_min, t_max, step_size``, use ``t_range``, which is an array of three numbers (``[t_min, t_max, step_size]``). ``dt`` was renamed to ``epsilon``. Other usage remains unchanged\n    - All ``TipableVMobject`` can pass in ``tip_length`` to control the style of ``tip``\n    - ``Line`` adds ``.set_points_by_ends(start, end, buff=0, path_arc=0)`` method\n    - ``Line`` added ``.get_projection(point)`` to return the projection position of ``point`` on a straight line\n    - ``Arrow`` adds three attributes of ``thickness, tip_width_ratio, tip_angle``\n    - ``CubicBezier`` is changed to ``a0, h0, h1, a1``, that is, only a third-order Bezier curve is supported\n    - ``Square`` can be initialized directly by passing in ``side_length`` instead of using the keyword ``side_length=``\n    - ``always_redraw(func, *args, **kwargs)`` supports incoming parameters ``*args, **kwargs``\n    - The ``digit_to_digit_buff`` property of ``DecimalNumber`` has been renamed to ``digit_buff_per_font_unit``, and the ``.scale()`` method has been improved\n    - ``ValueTracker`` adds ``value_type`` attribute, the default is ``np.float64``\n  \n- ``Scene``\n  \n  - Removed all functions of ``GraphScene`` (moved to ``once_useful_constructs``), ``MovingCameraScene``, ``ReconfigurableScene``, ``SceneFromVideo``, ``ZoomedScene``, and ``ThreeDScene``. Because these can basically be achieved by adjusting ``CameraFrame`` (``self.camera.frame``)\n  - Currently ``SampleSpaceScene`` and ``VectorScene`` have not been changed for the new version, so it is not recommended to use (only ``Scene`` is recommended)\n  - Fix the export of gif, just use the ``-i`` option directly\n  - Added the ``.interact()`` method, during which the mouse and keyboard can be used to continue the interaction, which will be executed by default after the scene ends\n  - Added ``.embed()`` method, open iPython terminal to enter interactive mode\n  - Added ``.save_state()`` method to save the current state of the scene\n  - Added ``.restore()`` method to restore the entire scene to the saved state\n  \n- ``utils``\n  \n  - A series of functions related to second-order Bezier have been added to ``utils/bezier.py``\n  - Added a function to read color map from ``matplotlib`` in ``utils/color.py``\n  - Added a series of related functions for processing folders/custom styles/object families\n  - ``resize_array``, ``resize_preserving_order``, ``resize_with_interpolation`` three functions have been added to ``utils/iterables.py``\n  - The definition of ``smooth`` is updated in ``utils/rate_functions.py``\n  - ``clip(a, min_a, max_a)`` function has been added to ``utils/simple_functions.py``\n  - Some functions have been improved in ``utils/space_ops.py``, some functions for space calculation, and functions for processing triangulation have been added\n  \n- ``constants``\n  \n  - Fixed the aspect ratio of the screen to 16:9\n  - Deleted the old gray series (``LIGHT_GREY``, ``GREY``, ``DARK_GREY``, ``DARKER_GREY``), added a new series of gray ``GREY_A`` ~ ``GREY_E``"
  },
  {
    "path": "docs/source/index.rst",
    "content": "Manim's documentation\n=====================\n\n.. image:: https://cdn.jsdelivr.net/gh/3b1b/manim@master/logo/white_with_name.png\n\nManim is an animation engine for explanatory math videos. It's used to create precise animations programmatically, as seen in the videos\nat `3Blue1Brown <https://www.3blue1brown.com/>`_.\n\nAnd here is a Chinese version of this documentation: https://docs.manim.org.cn/\n\n.. toctree::\n   :maxdepth: 2\n   :caption: Getting Started\n\n   getting_started/installation\n   getting_started/quickstart\n   getting_started/configuration\n   getting_started/example_scenes\n   getting_started/config\n   getting_started/structure\n   getting_started/whatsnew\n\n.. toctree::\n   :maxdepth: 2\n   :caption: Documentation\n\n   documentation/constants\n   documentation/custom_config\n\n.. toctree::\n   :maxdepth: 2\n   :caption: Development\n\n   development/changelog\n   development/contributing\n   development/about\n"
  },
  {
    "path": "docs/source/manim_example_ext.py",
    "content": "from docutils import nodes\nfrom docutils.parsers.rst import directives, Directive\n\nimport jinja2\nimport os\n\n\nclass skip_manim_node(nodes.Admonition, nodes.Element):\n    pass\n\n\ndef visit(self, node, name=\"\"):\n    self.visit_admonition(node, name)\n\n\ndef depart(self, node):\n    self.depart_admonition(node)\n\n\nclass ManimExampleDirective(Directive):\n    has_content = True\n    required_arguments = 1\n    optional_arguments = 0\n    option_spec = {\n        \"hide_code\": bool,\n        \"media\": str,\n    }\n    final_argument_whitespace = True\n\n    def run(self):\n        hide_code = \"hide_code\" in self.options\n        scene_name = self.arguments[0]\n        media_file_name = self.options[\"media\"]\n\n        source_block = [\n            \".. code-block:: python\",\n            \"\",\n            *[\"    \" + line for line in self.content],\n        ]\n        source_block = \"\\n\".join(source_block)\n\n        state_machine = self.state_machine\n        document = state_machine.document\n\n        if any(media_file_name.endswith(ext) for ext in [\".png\", \".jpg\", \".gif\"]):\n            is_video = False\n        else:\n            is_video = True\n\n        rendered_template = jinja2.Template(TEMPLATE).render(\n            scene_name=scene_name,\n            scene_name_lowercase=scene_name.lower(),\n            hide_code=hide_code,\n            is_video=is_video,\n            media_file_name=media_file_name,\n            source_block=source_block,\n        )\n        state_machine.insert_input(\n            rendered_template.split(\"\\n\"), source=document.attributes[\"source\"]\n        )\n\n        return []\n\n\ndef setup(app):\n    app.add_node(skip_manim_node, html=(visit, depart))\n\n    setup.app = app\n    setup.config = app.config\n    setup.confdir = app.confdir\n\n    app.add_directive(\"manim-example\", ManimExampleDirective)\n\n    metadata = {\"parallel_read_safe\": False, \"parallel_write_safe\": True}\n    return metadata\n\n\nTEMPLATE = r\"\"\"\n{% if not hide_code %}\n\n.. raw:: html\n\n    <div class=\"manim-example\">\n\n{% endif %}\n\n{% if is_video %}\n.. raw:: html\n\n    <video id=\"{{ scene_name_lowercase }}\" class=\"manim-video\" controls loop autoplay src=\"{{ media_file_name }}\"></video>\n{% else %}\n.. image:: {{ media_file_name }}\n    :align: center\n    :name: {{ scene_name_lowercase }}\n{% endif %}\n\n{% if not hide_code %}\n.. raw:: html\n\n    <h5 class=\"example-header\">{{ scene_name }}<a class=\"headerlink\" href=\"#{{ scene_name_lowercase }}\">¶</a></h5>\n\n{{ source_block }}\n{% endif %}\n\n.. raw:: html\n\n    </div>\n\"\"\""
  },
  {
    "path": "example_scenes.py",
    "content": "from manimlib import *\nimport numpy as np\n\n# To watch one of these scenes, run the following:\n# manimgl example_scenes.py OpeningManimExample\n# Use -s to skip to the end and just save the final frame\n# Use -w to write the animation to a file\n# Use -o to write it to a file and open it once done\n# Use -n <number> to skip ahead to the n'th animation of a scene.\n\n\nclass OpeningManimExample(Scene):\n    def construct(self):\n        intro_words = Text(\"\"\"\n            The original motivation for manim was to\n            better illustrate mathematical functions\n            as transformations.\n        \"\"\")\n        intro_words.to_edge(UP)\n\n        self.play(Write(intro_words))\n        self.wait(2)\n\n        # Linear transform\n        grid = NumberPlane((-10, 10), (-5, 5))\n        matrix = [[1, 1], [0, 1]]\n        linear_transform_words = VGroup(\n            Text(\"This is what the matrix\"),\n            IntegerMatrix(matrix),\n            Text(\"looks like\")\n        )\n        linear_transform_words.arrange(RIGHT)\n        linear_transform_words.to_edge(UP)\n        linear_transform_words.set_backstroke(width=5)\n\n        self.play(\n            ShowCreation(grid),\n            FadeTransform(intro_words, linear_transform_words)\n        )\n        self.wait()\n        self.play(grid.animate.apply_matrix(matrix), run_time=3)\n        self.wait()\n\n        # Complex map\n        c_grid = ComplexPlane()\n        moving_c_grid = c_grid.copy()\n        moving_c_grid.prepare_for_nonlinear_transform()\n        c_grid.set_stroke(BLUE_E, 1)\n        c_grid.add_coordinate_labels(font_size=24)\n        complex_map_words = TexText(\"\"\"\n            Or thinking of the plane as $\\\\mathds{C}$,\\\\\\\\\n            this is the map $z \\\\rightarrow z^2$\n        \"\"\")\n        complex_map_words.to_corner(UR)\n        complex_map_words.set_backstroke(width=5)\n\n        self.play(\n            FadeOut(grid),\n            Write(c_grid, run_time=3),\n            FadeIn(moving_c_grid),\n            FadeTransform(linear_transform_words, complex_map_words),\n        )\n        self.wait()\n        self.play(\n            moving_c_grid.animate.apply_complex_function(lambda z: z**2),\n            run_time=6,\n        )\n        self.wait(2)\n\n\nclass AnimatingMethods(Scene):\n    def construct(self):\n        grid = Tex(R\"\\pi\").get_grid(10, 10, height=4)\n        self.add(grid)\n\n        # You can animate the application of mobject methods with the\n        # \".animate\" syntax:\n        self.play(grid.animate.shift(LEFT))\n\n        # Both of those will interpolate between the mobject's initial\n        # state and whatever happens when you apply that method.\n        # For this example, calling grid.shift(LEFT) would shift the\n        # grid one unit to the left, but both of the previous calls to\n        # \"self.play\" animate that motion.\n\n        # The same applies for any method, including those setting colors.\n        self.play(grid.animate.set_color(YELLOW))\n        self.wait()\n        self.play(grid.animate.set_submobject_colors_by_gradient(BLUE, GREEN))\n        self.wait()\n        self.play(grid.animate.set_height(TAU - MED_SMALL_BUFF))\n        self.wait()\n\n        # The method Mobject.apply_complex_function lets you apply arbitrary\n        # complex functions, treating the points defining the mobject as\n        # complex numbers.\n        self.play(grid.animate.apply_complex_function(np.exp), run_time=5)\n        self.wait()\n\n        # Even more generally, you could apply Mobject.apply_function,\n        # which takes in functions form R^3 to R^3\n        self.play(\n            grid.animate.apply_function(\n                lambda p: [\n                    p[0] + 0.5 * math.sin(p[1]),\n                    p[1] + 0.5 * math.sin(p[0]),\n                    p[2]\n                ]\n            ),\n            run_time=5,\n        )\n        self.wait()\n\n\nclass TextExample(Scene):\n    def construct(self):\n        # To run this scene properly, you should have \"Consolas\" font in your computer\n        # for full usage, you can see https://github.com/3b1b/manim/pull/680\n        text = Text(\"Here is a text\", font=\"Consolas\", font_size=90)\n        difference = Text(\n            \"\"\"\n            The most important difference between Text and TexText is that\\n\n            you can change the font more easily, but can't use the LaTeX grammar\n            \"\"\",\n            font=\"Arial\", font_size=24,\n            # t2c is a dict that you can choose color for different text\n            t2c={\"Text\": BLUE, \"TexText\": BLUE, \"LaTeX\": ORANGE}\n        )\n        VGroup(text, difference).arrange(DOWN, buff=1)\n        self.play(Write(text))\n        self.play(FadeIn(difference, UP))\n        self.wait(3)\n\n        fonts = Text(\n            \"And you can also set the font according to different words\",\n            font=\"Arial\",\n            t2f={\"font\": \"Consolas\", \"words\": \"Consolas\"},\n            t2c={\"font\": BLUE, \"words\": GREEN}\n        )\n        fonts.set_width(FRAME_WIDTH - 1)\n        slant = Text(\n            \"And the same as slant and weight\",\n            font=\"Consolas\",\n            t2s={\"slant\": ITALIC},\n            t2w={\"weight\": BOLD},\n            t2c={\"slant\": ORANGE, \"weight\": RED}\n        )\n        VGroup(fonts, slant).arrange(DOWN, buff=0.8)\n        self.play(FadeOut(text), FadeOut(difference, shift=DOWN))\n        self.play(Write(fonts))\n        self.wait()\n        self.play(Write(slant))\n        self.wait()\n\n\nclass TexTransformExample(Scene):\n    def construct(self):\n        # Tex to color map\n        t2c = {\n            \"A\": BLUE,\n            \"B\": TEAL,\n            \"C\": GREEN,\n        }\n        # Configuration to pass along to each Tex mobject\n        kw = dict(font_size=72, t2c=t2c)\n        lines = VGroup(\n            Tex(\"A^2 + B^2 = C^2\", **kw),\n            Tex(\"A^2 = C^2 - B^2\", **kw),\n            Tex(\"A^2 = (C + B)(C - B)\", **kw),\n            Tex(R\"A = \\sqrt{(C + B)(C - B)}\", **kw),\n        )\n        lines.arrange(DOWN, buff=LARGE_BUFF)\n\n        self.add(lines[0])\n        # The animation TransformMatchingStrings will line up parts\n        # of the source and target which have matching substring strings.\n        # Here, giving it a little path_arc makes each part rotate into\n        # their final positions, which feels appropriate for the idea of\n        # rearranging an equation\n        self.play(\n            TransformMatchingStrings(\n                lines[0].copy(), lines[1],\n                # matched_keys specifies which substring should\n                # line up. If it's not specified, the animation\n                # will align the longest matching substrings.\n                # In this case, the substring \"^2 = C^2\" would\n                # trip it up\n                matched_keys=[\"A^2\", \"B^2\", \"C^2\"],\n                # When you want a substring from the source\n                # to go to a non-equal substring from the target,\n                # use the key map.\n                key_map={\"+\": \"-\"},\n                path_arc=90 * DEG,\n            ),\n        )\n        self.wait()\n        self.play(TransformMatchingStrings(\n            lines[1].copy(), lines[2],\n            matched_keys=[\"A^2\"]\n        ))\n        self.wait()\n        self.play(\n            TransformMatchingStrings(\n                lines[2].copy(), lines[3],\n                key_map={\"2\": R\"\\sqrt\"},\n                path_arc=-30 * DEG,\n            ),\n        )\n        self.wait(2)\n        self.play(LaggedStartMap(FadeOut, lines, shift=2 * RIGHT))\n\n        # TransformMatchingShapes will try to line up all pieces of a\n        # source mobject with those of a target, regardless of the\n        # what Mobject type they are.\n        source = Text(\"the morse code\", height=1)\n        target = Text(\"here come dots\", height=1)\n        saved_source = source.copy()\n\n        self.play(Write(source))\n        self.wait()\n        kw = dict(run_time=3, path_arc=PI / 2)\n        self.play(TransformMatchingShapes(source, target, **kw))\n        self.wait()\n        self.play(TransformMatchingShapes(target, saved_source, **kw))\n        self.wait()\n\n\nclass TexIndexing(Scene):\n    def construct(self):\n        # You can index into Tex mobject (or other StringMobjects) by substrings\n        equation = Tex(R\"e^{\\pi i} = -1\", font_size=144)\n\n        self.add(equation)\n        self.play(FlashAround(equation[\"e\"]))\n        self.wait()\n        self.play(Indicate(equation[R\"\\pi\"]))\n        self.wait()\n        self.play(TransformFromCopy(\n            equation[R\"e^{\\pi i}\"].copy().set_opacity(0.5),\n            equation[\"-1\"],\n            path_arc=-PI / 2,\n            run_time=3\n        ))\n        self.play(FadeOut(equation))\n\n        # Or regular expressions\n        equation = Tex(\"A^2 + B^2 = C^2\", font_size=144)\n\n        self.play(Write(equation))\n        for part in equation[re.compile(r\"\\w\\^2\")]:\n            self.play(FlashAround(part))\n        self.wait()\n        self.play(FadeOut(equation))\n        \n        # Indexing by substrings like this may not work when\n        # the order in which Latex draws symbols does not match\n        # the order in which they show up in the string.\n        # For example, here the infinity is drawn before the sigma\n        # so we don't get the desired behavior.\n        equation = Tex(R\"\\sum_{n = 1}^\\infty \\frac{1}{n^2} = \\frac{\\pi^2}{6}\", font_size=72)\n        self.play(FadeIn(equation))\n        self.play(equation[R\"\\infty\"].animate.set_color(RED))  # Doesn't hit the infinity\n        self.wait()\n        self.play(FadeOut(equation))\n\n        # However you can always fix this by explicitly passing in\n        # a string you might want to isolate later. Also, using\n        # \\over instead of \\frac helps to avoid the issue for fractions\n        equation = Tex(\n            R\"\\sum_{n = 1}^\\infty {1 \\over n^2} = {\\pi^2 \\over 6}\",\n            # Explicitly mark \"\\infty\" as a substring you might want to access\n            isolate=[R\"\\infty\"],\n            font_size=72\n        )\n        self.play(FadeIn(equation))\n        self.play(equation[R\"\\infty\"].animate.set_color(RED))  # Got it!\n        self.wait()\n        self.play(FadeOut(equation))\n\n\nclass UpdatersExample(Scene):\n    def construct(self):\n        square = Square()\n        square.set_fill(BLUE_E, 1)\n\n        # On all frames, the constructor Brace(square, UP) will\n        # be called, and the mobject brace will set its data to match\n        # that of the newly constructed object\n        brace = always_redraw(Brace, square, UP)\n\n        label = TexText(\"Width = 0.00\")\n        number = label.make_number_changeable(\"0.00\")\n\n        # This ensures that the method deicmal.next_to(square)\n        # is called on every frame\n        label.always.next_to(brace, UP)\n        # You could also write the following equivalent line\n        # label.add_updater(lambda m: m.next_to(brace, UP))\n\n        # If the argument itself might change, you can use f_always,\n        # for which the arguments following the initial Mobject method\n        # should be functions returning arguments to that method.\n        # The following line ensures thst decimal.set_value(square.get_y())\n        # is called every frame\n        number.f_always.set_value(square.get_width)\n        # You could also write the following equivalent line\n        # number.add_updater(lambda m: m.set_value(square.get_width()))\n\n        self.add(square, brace, label)\n\n        # Notice that the brace and label track with the square\n        self.play(\n            square.animate.scale(2),\n            rate_func=there_and_back,\n            run_time=2,\n        )\n        self.wait()\n        self.play(\n            square.animate.set_width(5, stretch=True),\n            run_time=3,\n        )\n        self.wait()\n        self.play(\n            square.animate.set_width(2),\n            run_time=3\n        )\n        self.wait()\n\n        # In general, you can always call Mobject.add_updater, and pass in\n        # a function that you want to be called on every frame.  The function\n        # should take in either one argument, the mobject, or two arguments,\n        # the mobject and the amount of time since the last frame.\n        now = self.time\n        w0 = square.get_width()\n        square.add_updater(\n            lambda m: m.set_width(w0 * math.sin(self.time - now) + w0)\n        )\n        self.wait(4 * PI)\n\n\nclass CoordinateSystemExample(Scene):\n    def construct(self):\n        axes = Axes(\n            # x-axis ranges from -1 to 10, with a default step size of 1\n            x_range=(-1, 10),\n            # y-axis ranges from -2 to 2 with a step size of 0.5\n            y_range=(-2, 2, 0.5),\n            # The axes will be stretched so as to match the specified\n            # height and width\n            height=6,\n            width=10,\n            # Axes is made of two NumberLine mobjects.  You can specify\n            # their configuration with axis_config\n            axis_config=dict(\n                stroke_color=GREY_A,\n                stroke_width=2,\n                numbers_to_exclude=[0],\n            ),\n            # Alternatively, you can specify configuration for just one\n            # of them, like this.\n            y_axis_config=dict(\n                big_tick_numbers=[-2, 2],\n            )\n        )\n        # Keyword arguments of add_coordinate_labels can be used to\n        # configure the DecimalNumber mobjects which it creates and\n        # adds to the axes\n        axes.add_coordinate_labels(\n            font_size=20,\n            num_decimal_places=1,\n        )\n        self.add(axes)\n\n        # Axes descends from the CoordinateSystem class, meaning\n        # you can call call axes.coords_to_point, abbreviated to\n        # axes.c2p, to associate a set of coordinates with a point,\n        # like so:\n        dot = Dot(fill_color=RED)\n        dot.move_to(axes.c2p(0, 0))\n        self.play(FadeIn(dot, scale=0.5))\n        self.play(dot.animate.move_to(axes.c2p(3, 2)))\n        self.wait()\n        self.play(dot.animate.move_to(axes.c2p(5, 0.5)))\n        self.wait()\n\n        # Similarly, you can call axes.point_to_coords, or axes.p2c\n        # print(axes.p2c(dot.get_center()))\n\n        # We can draw lines from the axes to better mark the coordinates\n        # of a given point.\n        # Here, the always_redraw command means that on each new frame\n        # the lines will be redrawn\n        h_line = always_redraw(lambda: axes.get_h_line(dot.get_left()))\n        v_line = always_redraw(lambda: axes.get_v_line(dot.get_bottom()))\n\n        self.play(\n            ShowCreation(h_line),\n            ShowCreation(v_line),\n        )\n        self.play(dot.animate.move_to(axes.c2p(3, -2)))\n        self.wait()\n        self.play(dot.animate.move_to(axes.c2p(1, 1)))\n        self.wait()\n\n        # If we tie the dot to a particular set of coordinates, notice\n        # that as we move the axes around it respects the coordinate\n        # system defined by them.\n        f_always(dot.move_to, lambda: axes.c2p(1, 1))\n        self.play(\n            axes.animate.scale(0.75).to_corner(UL),\n            run_time=2,\n        )\n        self.wait()\n        self.play(FadeOut(VGroup(axes, dot, h_line, v_line)))\n\n        # Other coordinate systems you can play around with include\n        # ThreeDAxes, NumberPlane, and ComplexPlane.\n\n\nclass GraphExample(Scene):\n    def construct(self):\n        axes = Axes((-3, 10), (-1, 8), height=6)\n        axes.add_coordinate_labels()\n\n        self.play(Write(axes, lag_ratio=0.01, run_time=1))\n\n        # Axes.get_graph will return the graph of a function\n        sin_graph = axes.get_graph(\n            lambda x: 2 * math.sin(x),\n            color=BLUE,\n        )\n        # By default, it draws it so as to somewhat smoothly interpolate\n        # between sampled points (x, f(x)).  If the graph is meant to have\n        # a corner, though, you can set use_smoothing to False\n        relu_graph = axes.get_graph(\n            lambda x: max(x, 0),\n            use_smoothing=False,\n            color=YELLOW,\n        )\n        # For discontinuous functions, you can specify the point of\n        # discontinuity so that it does not try to draw over the gap.\n        step_graph = axes.get_graph(\n            lambda x: 2.0 if x > 3 else 1.0,\n            discontinuities=[3],\n            color=GREEN,\n        )\n\n        # Axes.get_graph_label takes in either a string or a mobject.\n        # If it's a string, it treats it as a LaTeX expression.  By default\n        # it places the label next to the graph near the right side, and\n        # has it match the color of the graph\n        sin_label = axes.get_graph_label(sin_graph, \"\\\\sin(x)\")\n        relu_label = axes.get_graph_label(relu_graph, Text(\"ReLU\"))\n        step_label = axes.get_graph_label(step_graph, Text(\"Step\"), x=4)\n\n        self.play(\n            ShowCreation(sin_graph),\n            FadeIn(sin_label, RIGHT),\n        )\n        self.wait(2)\n        self.play(\n            ReplacementTransform(sin_graph, relu_graph),\n            FadeTransform(sin_label, relu_label),\n        )\n        self.wait()\n        self.play(\n            ReplacementTransform(relu_graph, step_graph),\n            FadeTransform(relu_label, step_label),\n        )\n        self.wait()\n\n        parabola = axes.get_graph(lambda x: 0.25 * x**2)\n        parabola.set_stroke(BLUE)\n        self.play(\n            FadeOut(step_graph),\n            FadeOut(step_label),\n            ShowCreation(parabola)\n        )\n        self.wait()\n\n        # You can use axes.input_to_graph_point, abbreviated\n        # to axes.i2gp, to find a particular point on a graph\n        dot = Dot(fill_color=RED)\n        dot.move_to(axes.i2gp(2, parabola))\n        self.play(FadeIn(dot, scale=0.5))\n\n        # A value tracker lets us animate a parameter, usually\n        # with the intent of having other mobjects update based\n        # on the parameter\n        x_tracker = ValueTracker(2)\n        dot.add_updater(lambda d: d.move_to(axes.i2gp(x_tracker.get_value(), parabola)))\n\n        self.play(x_tracker.animate.set_value(4), run_time=3)\n        self.play(x_tracker.animate.set_value(-2), run_time=3)\n        self.wait()\n\n\nclass TexAndNumbersExample(Scene):\n    def construct(self):\n        axes = Axes((-3, 3), (-3, 3), unit_size=1)\n        axes.to_edge(DOWN)\n        axes.add_coordinate_labels(font_size=16)\n        circle = Circle(radius=2)\n        circle.set_stroke(YELLOW, 3)\n        circle.move_to(axes.get_origin())\n        self.add(axes, circle)\n\n        # When numbers show up in tex, they can be readily\n        # replaced with DecimalMobjects so that methods like\n        # get_value and set_value can be called on them, and\n        # animations like ChangeDecimalToValue can be called\n        # on them.\n        tex = Tex(\"x^2 + y^2 = 4.00\")\n        tex.next_to(axes, UP, buff=0.5)\n        value = tex.make_number_changeable(\"4.00\")\n\n\n        # This will tie the right hand side of our equation to\n        # the square of the radius of the circle\n        value.add_updater(lambda v: v.set_value(circle.get_radius()**2))\n        self.add(tex)\n\n        text = Text(\"\"\"\n            You can manipulate numbers\n            in Tex mobjects\n        \"\"\", font_size=30)\n        text.next_to(tex, RIGHT, buff=1.5)\n        arrow = Arrow(text, tex)\n        self.add(text, arrow)\n\n        self.play(\n            circle.animate.set_height(2.0),\n            run_time=4,\n            rate_func=there_and_back,\n        )\n\n        # By default, tex.make_number_changeable replaces the first occurrence\n        # of the number,but by passing replace_all=True it replaces all and\n        # returns a group of the results\n        exponents = tex.make_number_changeable(\"2\", replace_all=True)\n        self.play(\n            LaggedStartMap(\n                FlashAround, exponents,\n                lag_ratio=0.2, buff=0.1, color=RED\n            ),\n            exponents.animate.set_color(RED)\n        )\n\n        def func(x, y):\n            # Switch from manim coords to axes coords\n            xa, ya = axes.point_to_coords(np.array([x, y, 0]))\n            return xa**4 + ya**4 - 4\n\n        new_curve = ImplicitFunction(func)\n        new_curve.match_style(circle)\n        circle.rotate(angle_of_vector(new_curve.get_start()))  # Align\n        value.clear_updaters()\n\n        self.play(\n            *(ChangeDecimalToValue(exp, 4) for exp in exponents),\n            ReplacementTransform(circle.copy(), new_curve),\n            circle.animate.set_stroke(width=1, opacity=0.5),\n        )\n\n\nclass SurfaceExample(ThreeDScene):\n    def construct(self):\n        surface_text = Text(\"For 3d scenes, try using surfaces\")\n        surface_text.fix_in_frame()\n        surface_text.to_edge(UP)\n        self.add(surface_text)\n        self.wait(0.1)\n\n        torus1 = Torus(r1=1, r2=1)\n        torus2 = Torus(r1=3, r2=1)\n        sphere = Sphere(radius=3, resolution=torus1.resolution)\n        # You can texture a surface with up to two images, which will\n        # be interpreted as the side towards the light, and away from\n        # the light.  These can be either urls, or paths to a local file\n        # in whatever you've set as the image directory in\n        # the custom_config.yml file\n\n        # day_texture = \"EarthTextureMap\"\n        # night_texture = \"NightEarthTextureMap\"\n        day_texture = \"https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Whole_world_-_land_and_oceans.jpg/1280px-Whole_world_-_land_and_oceans.jpg\"\n        night_texture = \"https://upload.wikimedia.org/wikipedia/commons/thumb/b/ba/The_earth_at_night.jpg/1280px-The_earth_at_night.jpg\"\n\n        surfaces = [\n            TexturedSurface(surface, day_texture, night_texture)\n            for surface in [sphere, torus1, torus2]\n        ]\n\n        for mob in surfaces:\n            mob.shift(IN)\n            mob.mesh = SurfaceMesh(mob)\n            mob.mesh.set_stroke(BLUE, 1, opacity=0.5)\n\n        surface = surfaces[0]\n\n        self.play(\n            FadeIn(surface),\n            ShowCreation(surface.mesh, lag_ratio=0.01, run_time=3),\n        )\n        for mob in surfaces:\n            mob.add(mob.mesh)\n        surface.save_state()\n        self.play(Rotate(surface, PI / 2), run_time=2)\n        for mob in surfaces[1:]:\n            mob.rotate(PI / 2)\n\n        self.play(\n            Transform(surface, surfaces[1]),\n            run_time=3\n        )\n\n        self.play(\n            Transform(surface, surfaces[2]),\n            # Move camera frame during the transition\n            self.frame.animate.increment_phi(-10 * DEG),\n            self.frame.animate.increment_theta(-20 * DEG),\n            run_time=3\n        )\n        # Add ambient rotation\n        self.frame.add_updater(lambda m, dt: m.increment_theta(-0.1 * dt))\n\n        # Play around with where the light is\n        light_text = Text(\"You can move around the light source\")\n        light_text.move_to(surface_text)\n        light_text.fix_in_frame()\n\n        self.play(FadeTransform(surface_text, light_text))\n        light = self.camera.light_source\n        light_dot = GlowDot(color=WHITE, radius=0.5)\n        light_dot.always.move_to(light)\n        self.add(light, light_dot)\n        light.save_state()\n        self.play(light.animate.move_to(3 * IN), run_time=5)\n        self.play(light.animate.shift(10 * OUT), run_time=5)\n\n        drag_text = Text(\"Try moving the mouse while pressing d or f\")\n        drag_text.move_to(light_text)\n        drag_text.fix_in_frame()\n\n        self.play(FadeTransform(light_text, drag_text))\n        self.wait()\n\n\nclass InteractiveDevelopment(Scene):\n    def construct(self):\n        circle = Circle()\n        circle.set_fill(BLUE, opacity=0.5)\n        circle.set_stroke(BLUE_E, width=4)\n        square = Square()\n\n        self.play(ShowCreation(square))\n        self.wait()\n\n        # This opens an iPython terminal where you can keep writing\n        # lines as if they were part of this construct method.\n        # In particular, 'square', 'circle' and 'self' will all be\n        # part of the local namespace in that terminal.\n        self.embed()\n\n        # Try copying and pasting some of the lines below into\n        # the interactive shell\n        self.play(ReplacementTransform(square, circle))\n        self.wait()\n        self.play(circle.animate.stretch(4, 0))\n        self.play(Rotate(circle, 90 * DEG))\n        self.play(circle.animate.shift(2 * RIGHT).scale(0.25))\n\n        text = Text(\"\"\"\n            In general, using the interactive shell\n            is very helpful when developing new scenes\n        \"\"\")\n        self.play(Write(text))\n\n        # In the interactive shell, you can just type\n        # play, add, remove, clear, wait, save_state and restore,\n        # instead of self.play, self.add, self.remove, etc.\n\n        # To interact with the window, type touch().  You can then\n        # scroll in the window, or zoom by holding down 'z' while scrolling,\n        # and change camera perspective by holding down 'd' while moving\n        # the mouse.  Press 'r' to reset to the standard camera position.\n        # Press 'q' to stop interacting with the window and go back to\n        # typing new commands into the shell.\n\n        # In principle you can customize a scene to be responsive to\n        # mouse and keyboard interactions\n        always(circle.move_to, self.mouse_point)\n\n\nclass ControlsExample(Scene):\n    drag_to_pan = False\n\n    def setup(self):\n        self.textbox = Textbox()\n        self.checkbox = Checkbox()\n        self.color_picker = ColorSliders()\n        self.panel = ControlPanel(\n            Text(\"Text\", font_size=24), self.textbox, Line(),\n            Text(\"Show/Hide Text\", font_size=24), self.checkbox, Line(),\n            Text(\"Color of Text\", font_size=24), self.color_picker\n        )\n        self.add(self.panel)\n\n    def construct(self):\n        text = Text(\"text\", font_size=96)\n\n        def text_updater(old_text):\n            assert(isinstance(old_text, Text))\n            new_text = Text(self.textbox.get_value(), font_size=old_text.font_size)\n            # new_text.align_data_and_family(old_text)\n            new_text.move_to(old_text)\n            if self.checkbox.get_value():\n                new_text.set_fill(\n                    color=self.color_picker.get_picked_color(),\n                    opacity=self.color_picker.get_picked_opacity()\n                )\n            else:\n                new_text.set_opacity(0)\n            old_text.become(new_text)\n\n        text.add_updater(text_updater)\n\n        self.add(MotionMobject(text))\n\n        self.textbox.set_value(\"Manim\")\n        # self.wait(60)\n        # self.embed()\n\n\n# See https://github.com/3b1b/videos for many, many more\n"
  },
  {
    "path": "logo/logo.py",
    "content": "from manimlib.imports import *\n\nNEW_BLUE = \"#68a8e1\"\n\nclass Thumbnail(GraphScene):\n    CONFIG = {\n        \"y_max\": 8,\n        \"y_axis_height\": 5,\n    }\n\n    def construct(self):\n        self.show_function_graph()\n\n    def show_function_graph(self):\n        self.setup_axes(animate=False)\n        def func(x):\n            return 0.1 * (x + 3-5) * (x - 3-5) * (x-5) + 5\n\n        def rect(x):\n            return 2.775*(x-1.5)+3.862\n        recta = self.get_graph(rect,x_min=-1,x_max=5)\n        graph = self.get_graph(func,x_min=0.2,x_max=9)\n        graph.set_color(NEW_BLUE)\n        input_tracker_p1 = ValueTracker(1.5)\n        input_tracker_p2 = ValueTracker(3.5)\n\n        def get_x_value(input_tracker):\n            return input_tracker.get_value()\n\n        def get_y_value(input_tracker):\n            return graph.underlying_function(get_x_value(input_tracker))\n\n        def get_x_point(input_tracker):\n            return self.coords_to_point(get_x_value(input_tracker), 0)\n\n        def get_y_point(input_tracker):\n            return self.coords_to_point(0, get_y_value(input_tracker))\n\n        def get_graph_point(input_tracker):\n            return self.coords_to_point(get_x_value(input_tracker), get_y_value(input_tracker))\n\n        def get_v_line(input_tracker):\n            return DashedLine(get_x_point(input_tracker), get_graph_point(input_tracker), stroke_width=2)\n\n        def get_h_line(input_tracker):\n            return DashedLine(get_graph_point(input_tracker), get_y_point(input_tracker), stroke_width=2)\n        # \n        input_triangle_p1 = RegularPolygon(n=3, start_angle=TAU / 4)\n        output_triangle_p1 = RegularPolygon(n=3, start_angle=0)\n        for triangle in input_triangle_p1, output_triangle_p1:\n            triangle.set_fill(WHITE, 1)\n            triangle.set_stroke(width=0)\n            triangle.scale(0.1)\n        # \n        input_triangle_p2 = RegularPolygon(n=3, start_angle=TAU / 4)\n        output_triangle_p2 = RegularPolygon(n=3, start_angle=0)\n        for triangle in input_triangle_p2, output_triangle_p2:\n            triangle.set_fill(WHITE, 1)\n            triangle.set_stroke(width=0)\n            triangle.scale(0.1)\n        \n        # \n        x_label_p1 = Tex(\"a\")\n        output_label_p1 = Tex(\"f(a)\")\n        x_label_p2 = Tex(\"b\")\n        output_label_p2 = Tex(\"f(b)\")\n        v_line_p1 = get_v_line(input_tracker_p1)\n        v_line_p2 = get_v_line(input_tracker_p2)\n        h_line_p1 = get_h_line(input_tracker_p1)\n        h_line_p2 = get_h_line(input_tracker_p2)\n        graph_dot_p1 = Dot(color=WHITE)\n        graph_dot_p2 = Dot(color=WHITE)\n\n        # reposition mobjects\n        x_label_p1.next_to(v_line_p1, DOWN)\n        x_label_p2.next_to(v_line_p2, DOWN)\n        output_label_p1.next_to(h_line_p1, LEFT)\n        output_label_p2.next_to(h_line_p2, LEFT)\n        input_triangle_p1.next_to(v_line_p1, DOWN, buff=0)\n        input_triangle_p2.next_to(v_line_p2, DOWN, buff=0)\n        output_triangle_p1.next_to(h_line_p1, LEFT, buff=0)\n        output_triangle_p2.next_to(h_line_p2, LEFT, buff=0)\n        graph_dot_p1.move_to(get_graph_point(input_tracker_p1))\n        graph_dot_p2.move_to(get_graph_point(input_tracker_p2))\n\n\n        #\n        self.play(\n            ShowCreation(graph),\n        )\n        # Animacion del punto a\n        self.add_foreground_mobject(graph_dot_p1)\n        self.add_foreground_mobject(graph_dot_p2)\n        self.play(\n            DrawBorderThenFill(input_triangle_p1),\n            Write(x_label_p1),\n            ShowCreation(v_line_p1),\n            GrowFromCenter(graph_dot_p1),\n            ShowCreation(h_line_p1),\n            Write(output_label_p1),\n            DrawBorderThenFill(output_triangle_p1),\n            DrawBorderThenFill(input_triangle_p2),\n            Write(x_label_p2),\n            ShowCreation(v_line_p2),\n            GrowFromCenter(graph_dot_p2),\n            ShowCreation(h_line_p2),\n            Write(output_label_p2),\n            DrawBorderThenFill(output_triangle_p2),\n            run_time=0.5\n        )\n        self.add(\n            input_triangle_p2,\n            x_label_p2,\n            graph_dot_p2,\n            v_line_p2,\n            h_line_p2,\n            output_triangle_p2,\n            output_label_p2,\n        )\n        ###################\n        pendiente_recta = self.get_secant_slope_group(\n            1.9, recta, dx = 1.4,\n            df_label = None,\n            dx_label = None,\n            dx_line_color = PURPLE,\n            df_line_color= ORANGE,\n            )\n        grupo_secante = self.get_secant_slope_group(\n            1.5, graph, dx = 2,\n            df_label = None,\n            dx_label = None,\n            dx_line_color = \"#942357\",\n            df_line_color= \"#3f7d5c\",\n            secant_line_color = RED,\n        )\n\n\n        self.add(\n            input_triangle_p2,\n            graph_dot_p2,\n            v_line_p2,\n            h_line_p2,\n            output_triangle_p2,\n        )\n        self.play(FadeIn(grupo_secante))\n\n        kwargs = {\n            \"x_min\" : 4,\n            \"x_max\" : 9,\n            \"fill_opacity\" : 0.75,\n            \"stroke_width\" : 0.25,\n        }\n        self.graph=graph\n        iteraciones=6\n\n\n        self.rect_list = self.get_riemann_rectangles_list(\n            graph, iteraciones,start_color=PURPLE,end_color=ORANGE, **kwargs\n        )\n        flat_rects = self.get_riemann_rectangles(\n            self.get_graph(lambda x : 0), dx = 0.5,start_color=invert_color(PURPLE),end_color=invert_color(ORANGE),**kwargs\n        )\n        rects = self.rect_list[0]\n        self.transform_between_riemann_rects(\n            flat_rects, rects, \n            replace_mobject_with_target_in_scene = True,\n            run_time=0.9\n        )\n\n        # adding manim\n        picture = Group(*self.mobjects)\n        picture.scale(0.6).to_edge(LEFT, buff=SMALL_BUFF)\n        manim = TexText(\"Manim\").set_height(1.5) \\\n                                    .next_to(picture, RIGHT) \\\n                                    .shift(DOWN * 0.7)\n        self.add(manim)\n"
  },
  {
    "path": "manimlib/__init__.py",
    "content": "try:\n    from importlib.metadata import version, PackageNotFoundError\nexcept ImportError:  # For Python <3.8 fallback\n    from importlib_metadata import version, PackageNotFoundError  # type: ignore\n\ntry:\n    __version__ = version(\"manimgl\")\nexcept PackageNotFoundError:\n    __version__ = \"unknown\"\n\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from manimlib.typing import *\n\nfrom manimlib.constants import *\n\nfrom manimlib.window import *\n\nfrom manimlib.animation.animation import *\nfrom manimlib.animation.composition import *\nfrom manimlib.animation.creation import *\nfrom manimlib.animation.fading import *\nfrom manimlib.animation.growing import *\nfrom manimlib.animation.indication import *\nfrom manimlib.animation.movement import *\nfrom manimlib.animation.numbers import *\nfrom manimlib.animation.rotation import *\nfrom manimlib.animation.specialized import *\nfrom manimlib.animation.transform import *\nfrom manimlib.animation.transform_matching_parts import *\nfrom manimlib.animation.update import *\n\nfrom manimlib.camera.camera import *\n\nfrom manimlib.mobject.boolean_ops import *\nfrom manimlib.mobject.changing import *\nfrom manimlib.mobject.coordinate_systems import *\nfrom manimlib.mobject.frame import *\nfrom manimlib.mobject.functions import *\nfrom manimlib.mobject.geometry import *\nfrom manimlib.mobject.interactive import *\nfrom manimlib.mobject.matrix import *\nfrom manimlib.mobject.mobject import *\nfrom manimlib.mobject.mobject_update_utils import *\nfrom manimlib.mobject.number_line import *\nfrom manimlib.mobject.numbers import *\nfrom manimlib.mobject.probability import *\nfrom manimlib.mobject.shape_matchers import *\nfrom manimlib.mobject.svg.brace import *\nfrom manimlib.mobject.svg.drawings import *\nfrom manimlib.mobject.svg.string_mobject import *\nfrom manimlib.mobject.svg.svg_mobject import *\nfrom manimlib.mobject.svg.special_tex import *\nfrom manimlib.mobject.svg.tex_mobject import *\nfrom manimlib.mobject.svg.text_mobject import *\nfrom manimlib.mobject.three_dimensions import *\nfrom manimlib.mobject.types.dot_cloud import *\nfrom manimlib.mobject.types.image_mobject import *\nfrom manimlib.mobject.types.point_cloud_mobject import *\nfrom manimlib.mobject.types.surface import *\nfrom manimlib.mobject.types.vectorized_mobject import *\nfrom manimlib.mobject.value_tracker import *\nfrom manimlib.mobject.vector_field import *\n\nfrom manimlib.scene.interactive_scene import *\nfrom manimlib.scene.scene import *\n\nfrom manimlib.utils.bezier import *\nfrom manimlib.utils.cache import *\nfrom manimlib.utils.color import *\nfrom manimlib.utils.dict_ops import *\nfrom manimlib.utils.debug import *\nfrom manimlib.utils.directories import *\nfrom manimlib.utils.file_ops import *\nfrom manimlib.utils.images import *\nfrom manimlib.utils.iterables import *\nfrom manimlib.utils.paths import *\nfrom manimlib.utils.rate_functions import *\nfrom manimlib.utils.simple_functions import *\nfrom manimlib.utils.shaders import *\nfrom manimlib.utils.sounds import *\nfrom manimlib.utils.space_ops import *\nfrom manimlib.utils.tex import *\n"
  },
  {
    "path": "manimlib/__main__.py",
    "content": "#!/usr/bin/env python\nfrom addict import Dict\n\nfrom manimlib import __version__\nfrom manimlib.config import manim_config\nfrom manimlib.config import parse_cli\nimport manimlib.extract_scene\nfrom manimlib.utils.cache import clear_cache\nfrom manimlib.window import Window\n\n\nfrom IPython.terminal.embed import KillEmbedded\n\n\nfrom typing import TYPE_CHECKING\nif TYPE_CHECKING:\n    from argparse import Namespace\n\n\ndef run_scenes():\n    \"\"\"\n    Runs the scenes in a loop and detects when a scene reload is requested.\n    \"\"\"\n    # Create a new dict to be able to upate without\n    # altering global configuration\n    scene_config = Dict(manim_config.scene)\n    run_config = manim_config.run\n\n    if run_config.show_in_window:\n        # Create a reusable window\n        window = Window(**manim_config.window)\n        scene_config.update(window=window)\n\n    while True:\n        try:\n            # Blocking call since a scene may init an IPython shell()\n            scenes = manimlib.extract_scene.main(scene_config, run_config)\n            for scene in scenes:\n                scene.run()\n            return\n        except KillEmbedded:\n            # Requested via the `exit_raise` IPython runline magic\n            # by means of the reload_scene() command\n            pass\n        except KeyboardInterrupt:\n            break\n\n\ndef main():\n    \"\"\"\n    Main entry point for ManimGL.\n    \"\"\"\n    print(f\"ManimGL \\033[32mv{__version__}\\033[0m\")\n\n    args = parse_cli()\n    if args.version and args.file is None:\n        return\n    if args.clear_cache:\n        clear_cache()\n\n    run_scenes()\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "manimlib/animation/__init__.py",
    "content": ""
  },
  {
    "path": "manimlib/animation/animation.py",
    "content": "from __future__ import annotations\n\nfrom copy import deepcopy\n\nfrom manimlib.mobject.mobject import _AnimationBuilder\nfrom manimlib.mobject.mobject import Mobject\nfrom manimlib.utils.iterables import remove_list_redundancies\nfrom manimlib.utils.rate_functions import smooth\nfrom manimlib.utils.simple_functions import clip\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable\n\n    from manimlib.scene.scene import Scene\n\n\nDEFAULT_ANIMATION_RUN_TIME = 1.0\nDEFAULT_ANIMATION_LAG_RATIO = 0\n\n\nclass Animation(object):\n    def __init__(\n        self,\n        mobject: Mobject,\n        run_time: float = DEFAULT_ANIMATION_RUN_TIME,\n        # Tuple of times, between which the animation will run\n        time_span: tuple[float, float] | None = None,\n        # If 0, the animation is applied to all submobjects at the same time\n        # If 1, it is applied to each successively.\n        # If 0 < lag_ratio < 1, its applied to each with lagged start times\n        lag_ratio: float = DEFAULT_ANIMATION_LAG_RATIO,\n        rate_func: Callable[[float], float] = smooth,\n        name: str = \"\",\n        # Does this animation add or remove a mobject from the screen\n        remover: bool = False,\n        # What to enter into the update function upon completion\n        final_alpha_value: float = 1.0,\n        # If set to True, the mobject itself will have its internal updaters called,\n        # but the start or target mobjects would not be suspended. To completely suspend\n        # updating, call mobject.suspend_updating() before the animation\n        suspend_mobject_updating: bool = False,\n    ):\n        self._validate_input_type(mobject)\n        self.mobject = mobject\n        self.run_time = run_time\n        self.time_span = time_span\n        self.rate_func = rate_func\n        self.name = name or self.__class__.__name__ + str(self.mobject)\n        self.remover = remover\n        self.final_alpha_value = final_alpha_value\n        self.lag_ratio = lag_ratio\n        self.suspend_mobject_updating = suspend_mobject_updating\n\n    def _validate_input_type(self, mobject: Mobject) -> None:\n        if not isinstance(mobject, Mobject):\n            raise TypeError(\"Animation only works for Mobjects.\")\n\n    def __str__(self) -> str:\n        return self.name\n\n    def begin(self) -> None:\n        # This is called right as an animation is being\n        # played.  As much initialization as possible,\n        # especially any mobject copying, should live in\n        # this method\n        if self.time_span is not None:\n            start, end = self.time_span\n            self.run_time = max(end, self.run_time)\n        self.mobject.set_animating_status(True)\n        self.starting_mobject = self.create_starting_mobject()\n        if self.suspend_mobject_updating:\n            self.mobject_was_updating = not self.mobject.updating_suspended\n            self.mobject.suspend_updating()\n        self.families = list(self.get_all_families_zipped())\n        self.interpolate(0)\n\n    def finish(self) -> None:\n        self.interpolate(self.final_alpha_value)\n        self.mobject.set_animating_status(False)\n        if self.suspend_mobject_updating and self.mobject_was_updating:\n            self.mobject.resume_updating()\n\n    def clean_up_from_scene(self, scene: Scene) -> None:\n        if self.is_remover():\n            scene.remove(self.mobject)\n\n    def create_starting_mobject(self) -> Mobject:\n        # Keep track of where the mobject starts\n        return self.mobject.copy()\n\n    def get_all_mobjects(self) -> tuple[Mobject, Mobject]:\n        \"\"\"\n        Ordering must match the ording of arguments to interpolate_submobject\n        \"\"\"\n        return self.mobject, self.starting_mobject\n\n    def get_all_families_zipped(self) -> zip[tuple[Mobject]]:\n        return zip(*[\n            mob.get_family()\n            for mob in self.get_all_mobjects()\n        ])\n\n    def update_mobjects(self, dt: float) -> None:\n        \"\"\"\n        Updates things like starting_mobject, and (for\n        Transforms) target_mobject.\n        \"\"\"\n        for mob in self.get_all_mobjects_to_update():\n            mob.update(dt)\n\n    def get_all_mobjects_to_update(self) -> list[Mobject]:\n        # The surrounding scene typically handles\n        # updating of self.mobject.\n        items = list(filter(\n            lambda m: m is not self.mobject,\n            self.get_all_mobjects()\n        ))\n        items = remove_list_redundancies(items)\n        return items\n\n    def copy(self):\n        return deepcopy(self)\n\n    def update_rate_info(\n        self,\n        run_time: float | None = None,\n        rate_func: Callable[[float], float] | None = None,\n        lag_ratio: float | None = None,\n    ):\n        self.run_time = run_time or self.run_time\n        self.rate_func = rate_func or self.rate_func\n        self.lag_ratio = lag_ratio or self.lag_ratio\n        return self\n\n    # Methods for interpolation, the mean of an Animation\n    def interpolate(self, alpha: float) -> None:\n        self.interpolate_mobject(alpha)\n\n    def update(self, alpha: float) -> None:\n        \"\"\"\n        This method shouldn't exist, but it's here to\n        keep many old scenes from breaking\n        \"\"\"\n        self.interpolate(alpha)\n\n    def time_spanned_alpha(self, alpha: float) -> float:\n        if self.time_span is not None:\n            start, end = self.time_span\n            return clip(alpha * self.run_time - start, 0, end - start) / (end - start)\n        return alpha\n\n    def interpolate_mobject(self, alpha: float) -> None:\n        for i, mobs in enumerate(self.families):\n            sub_alpha = self.get_sub_alpha(self.time_spanned_alpha(alpha), i, len(self.families))\n            self.interpolate_submobject(*mobs, sub_alpha)\n\n    def interpolate_submobject(\n        self,\n        submobject: Mobject,\n        starting_submobject: Mobject,\n        alpha: float\n    ):\n        # Typically ipmlemented by subclass\n        pass\n\n    def get_sub_alpha(\n        self,\n        alpha: float,\n        index: int,\n        num_submobjects: int\n    ) -> float:\n        # TODO, make this more understanable, and/or combine\n        # its functionality with AnimationGroup's method\n        # build_animations_with_timings\n        lag_ratio = self.lag_ratio\n        full_length = (num_submobjects - 1) * lag_ratio + 1\n        value = alpha * full_length\n        lower = index * lag_ratio\n        raw_sub_alpha = clip((value - lower), 0, 1)\n        return self.rate_func(raw_sub_alpha)\n\n    # Getters and setters\n    def set_run_time(self, run_time: float):\n        self.run_time = run_time\n        return self\n\n    def get_run_time(self) -> float:\n        if self.time_span:\n            return max(self.run_time, self.time_span[1])\n        return self.run_time\n\n    def set_rate_func(self, rate_func: Callable[[float], float]):\n        self.rate_func = rate_func\n        return self\n\n    def get_rate_func(self) -> Callable[[float], float]:\n        return self.rate_func\n\n    def set_name(self, name: str):\n        self.name = name\n        return self\n\n    def is_remover(self) -> bool:\n        return self.remover\n\n\ndef prepare_animation(anim: Animation | _AnimationBuilder):\n    if isinstance(anim, _AnimationBuilder):\n        return anim.build()\n\n    if isinstance(anim, Animation):\n        return anim\n\n    raise TypeError(f\"Object {anim} cannot be converted to an animation\")\n"
  },
  {
    "path": "manimlib/animation/composition.py",
    "content": "from __future__ import annotations\n\nfrom manimlib.animation.animation import Animation\nfrom manimlib.animation.animation import prepare_animation\nfrom manimlib.mobject.mobject import _AnimationBuilder\nfrom manimlib.mobject.mobject import Group\nfrom manimlib.mobject.types.vectorized_mobject import VGroup\nfrom manimlib.mobject.types.vectorized_mobject import VMobject\nfrom manimlib.utils.bezier import integer_interpolate\nfrom manimlib.utils.bezier import interpolate\nfrom manimlib.utils.iterables import remove_list_redundancies\nfrom manimlib.utils.simple_functions import clip\n\nfrom typing import TYPE_CHECKING, Union, Iterable\nAnimationType = Union[Animation, _AnimationBuilder]\n\nif TYPE_CHECKING:\n    from typing import Callable, Optional\n\n    from manimlib.mobject.mobject import Mobject\n    from manimlib.scene.scene import Scene\n\n\nDEFAULT_LAGGED_START_LAG_RATIO = 0.05\n\n\nclass AnimationGroup(Animation):\n    def __init__(\n        self,\n        *args: AnimationType | Iterable[AnimationType],\n        run_time: float = -1,  # If negative, default to sum of inputed animation runtimes\n        lag_ratio: float = 0.0,\n        group: Optional[Mobject] = None,\n        group_type: Optional[type] = None,\n        **kwargs\n    ):\n        animations = args[0] if isinstance(args[0], Iterable) else args\n        self.animations = [prepare_animation(anim) for anim in animations]\n        self.build_animations_with_timings(lag_ratio)\n        self.max_end_time = max((awt[2] for awt in self.anims_with_timings), default=0)\n        self.run_time = self.max_end_time if run_time < 0 else run_time\n        self.lag_ratio = lag_ratio\n        mobs = remove_list_redundancies([a.mobject for a in self.animations])\n        if group is not None:\n            self.group = group\n        elif group_type is not None:\n            self.group = group_type(*mobs)\n        elif all(isinstance(anim.mobject, VMobject) for anim in animations):\n            self.group = VGroup(*mobs)\n        else:\n            self.group = Group(*mobs)\n\n        super().__init__(\n            self.group,\n            run_time=self.run_time,\n            lag_ratio=lag_ratio,\n            **kwargs\n        )\n\n    def get_all_mobjects(self) -> Mobject:\n        return self.group\n\n    def begin(self) -> None:\n        self.group.set_animating_status(True)\n        for anim in self.animations:\n            anim.begin()\n        # self.init_run_time()\n\n    def finish(self) -> None:\n        self.group.set_animating_status(False)\n        for anim in self.animations:\n            anim.finish()\n\n    def clean_up_from_scene(self, scene: Scene) -> None:\n        for anim in self.animations:\n            anim.clean_up_from_scene(scene)\n\n    def update_mobjects(self, dt: float) -> None:\n        for anim in self.animations:\n            anim.update_mobjects(dt)\n\n    def calculate_max_end_time(self) -> None:\n        self.max_end_time = max(\n            (awt[2] for awt in self.anims_with_timings),\n            default=0,\n        )\n        if self.run_time < 0:\n            self.run_time = self.max_end_time\n\n    def build_animations_with_timings(self, lag_ratio: float) -> None:\n        \"\"\"\n        Creates a list of triplets of the form\n        (anim, start_time, end_time)\n        \"\"\"\n        self.anims_with_timings = []\n        curr_time = 0\n        for anim in self.animations:\n            start_time = curr_time\n            end_time = start_time + anim.get_run_time()\n            self.anims_with_timings.append(\n                (anim, start_time, end_time)\n            )\n            # Start time of next animation is based on the lag_ratio\n            curr_time = interpolate(\n                start_time, end_time, lag_ratio\n            )\n\n    def interpolate(self, alpha: float) -> None:\n        # Note, if the run_time of AnimationGroup has been\n        # set to something other than its default, these\n        # times might not correspond to actual times,\n        # e.g. of the surrounding scene.  Instead they'd\n        # be a rescaled version.  But that's okay!\n        time = alpha * self.max_end_time\n        for anim, start_time, end_time in self.anims_with_timings:\n            anim_time = end_time - start_time\n            if anim_time == 0:\n                sub_alpha = 0\n            else:\n                sub_alpha = clip((time - start_time) / anim_time, 0, 1)\n            anim.interpolate(sub_alpha)\n\n\nclass Succession(AnimationGroup):\n    def __init__(\n        self,\n        *animations: Animation,\n        lag_ratio: float = 1.0,\n        **kwargs\n    ):\n        super().__init__(*animations, lag_ratio=lag_ratio, **kwargs)\n\n    def begin(self) -> None:\n        assert len(self.animations) > 0\n        self.active_animation = self.animations[0]\n        self.active_animation.begin()\n\n    def finish(self) -> None:\n        self.active_animation.finish()\n\n    def update_mobjects(self, dt: float) -> None:\n        self.active_animation.update_mobjects(dt)\n\n    def interpolate(self, alpha: float) -> None:\n        index, subalpha = integer_interpolate(\n            0, len(self.animations), alpha\n        )\n        animation = self.animations[index]\n        if animation is not self.active_animation:\n            self.active_animation.finish()\n            animation.begin()\n            self.active_animation = animation\n        animation.interpolate(subalpha)\n\n\nclass LaggedStart(AnimationGroup):\n    def __init__(\n        self,\n        *animations,\n        lag_ratio: float = DEFAULT_LAGGED_START_LAG_RATIO,\n        **kwargs\n    ):\n        super().__init__(*animations, lag_ratio=lag_ratio, **kwargs)\n\n\nclass LaggedStartMap(LaggedStart):\n    def __init__(\n        self,\n        anim_func: Callable[[Mobject], Animation],\n        group: Mobject,\n        run_time: float = 2.0,\n        lag_ratio: float = DEFAULT_LAGGED_START_LAG_RATIO,\n        **kwargs\n    ):\n        anim_kwargs = dict(kwargs)\n        anim_kwargs.pop(\"lag_ratio\", None)\n        super().__init__(\n            *(anim_func(submob, **anim_kwargs) for submob in group),\n            run_time=run_time,\n            lag_ratio=lag_ratio,\n            group=group\n        )\n"
  },
  {
    "path": "manimlib/animation/creation.py",
    "content": "from __future__ import annotations\n\nfrom abc import ABC, abstractmethod\n\nimport numpy as np\n\nfrom manimlib.animation.animation import Animation\nfrom manimlib.mobject.svg.string_mobject import StringMobject\nfrom manimlib.mobject.types.vectorized_mobject import VMobject\nfrom manimlib.utils.bezier import integer_interpolate\nfrom manimlib.utils.rate_functions import linear\nfrom manimlib.utils.rate_functions import double_smooth\nfrom manimlib.utils.rate_functions import smooth\nfrom manimlib.utils.simple_functions import clip\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable\n    from manimlib.mobject.mobject import Mobject\n    from manimlib.scene.scene import Scene\n    from manimlib.typing import ManimColor\n\n\nclass ShowPartial(Animation, ABC):\n    \"\"\"\n    Abstract class for ShowCreation and ShowPassingFlash\n    \"\"\"\n    def __init__(self, mobject: Mobject, should_match_start: bool = False, **kwargs):\n        self.should_match_start = should_match_start\n        super().__init__(mobject, **kwargs)\n\n    def interpolate_submobject(\n        self,\n        submob: Mobject,\n        start_submob: Mobject,\n        alpha: float\n    ) -> None:\n        submob.pointwise_become_partial(\n            start_submob, *self.get_bounds(alpha)\n        )\n\n    @abstractmethod\n    def get_bounds(self, alpha: float) -> tuple[float, float]:\n        raise Exception(\"Not Implemented\")\n\n\nclass ShowCreation(ShowPartial):\n    def __init__(self, mobject: Mobject, lag_ratio: float = 1.0, **kwargs):\n        super().__init__(mobject, lag_ratio=lag_ratio, **kwargs)\n\n    def get_bounds(self, alpha: float) -> tuple[float, float]:\n        return (0, alpha)\n\n\nclass Uncreate(ShowCreation):\n    def __init__(\n        self,\n        mobject: Mobject,\n        rate_func: Callable[[float], float] = lambda t: smooth(1 - t),\n        remover: bool = True,\n        should_match_start: bool = True,\n        **kwargs,\n    ):\n        super().__init__(\n            mobject,\n            rate_func=rate_func,\n            remover=remover,\n            should_match_start=should_match_start,\n            **kwargs,\n        )\n\n\nclass DrawBorderThenFill(Animation):\n    def __init__(\n        self,\n        vmobject: VMobject,\n        run_time: float = 2.0,\n        rate_func: Callable[[float], float] = double_smooth,\n        stroke_width: float = 2.0,\n        stroke_color: ManimColor = None,\n        draw_border_animation_config: dict = {},\n        fill_animation_config: dict = {},\n        **kwargs\n    ):\n        assert isinstance(vmobject, VMobject)\n        self.sm_to_index = {hash(sm): 0 for sm in vmobject.get_family()}\n        self.stroke_width = stroke_width\n        self.stroke_color = stroke_color\n        self.draw_border_animation_config = draw_border_animation_config\n        self.fill_animation_config = fill_animation_config\n        super().__init__(\n            vmobject,\n            run_time=run_time,\n            rate_func=rate_func,\n            **kwargs\n        )\n        self.mobject = vmobject\n\n    def begin(self) -> None:\n        self.mobject.set_animating_status(True)\n        self.outline = self.get_outline()\n        super().begin()\n        self.mobject.match_style(self.outline)\n\n    def finish(self) -> None:\n        super().finish()\n        self.mobject.refresh_joint_angles()\n\n    def get_outline(self) -> VMobject:\n        outline = self.mobject.copy()\n        outline.set_fill(opacity=0)\n        for sm in outline.family_members_with_points():\n            sm.set_stroke(\n                color=self.stroke_color or sm.get_stroke_color(),\n                width=self.stroke_width,\n                behind=self.mobject.stroke_behind,\n            )\n        return outline\n\n    def get_all_mobjects(self) -> list[Mobject]:\n        return [*super().get_all_mobjects(), self.outline]\n\n    def interpolate_submobject(\n        self,\n        submob: VMobject,\n        start: VMobject,\n        outline: VMobject,\n        alpha: float\n    ) -> None:\n        index, subalpha = integer_interpolate(0, 2, alpha)\n\n        if index == 1 and self.sm_to_index[hash(submob)] == 0:\n            # First time crossing over\n            submob.set_data(outline.data)\n            self.sm_to_index[hash(submob)] = 1\n\n        if index == 0:\n            submob.pointwise_become_partial(outline, 0, subalpha)\n        else:\n            submob.interpolate(outline, start, subalpha)\n\n\nclass Write(DrawBorderThenFill):\n    def __init__(\n        self,\n        vmobject: VMobject,\n        run_time: float = -1,  # If negative, this will be reassigned\n        lag_ratio: float = -1,  # If negative, this will be reassigned\n        rate_func: Callable[[float], float] = linear,\n        stroke_color: ManimColor = None,\n        **kwargs\n    ):\n        if stroke_color is None:\n            stroke_color = vmobject.get_color()\n        family_size = len(vmobject.family_members_with_points())\n        super().__init__(\n            vmobject,\n            run_time=self.compute_run_time(family_size, run_time),\n            lag_ratio=self.compute_lag_ratio(family_size, lag_ratio),\n            rate_func=rate_func,\n            stroke_color=stroke_color,\n            **kwargs\n        )\n\n    def compute_run_time(self, family_size: int, run_time: float):\n        if run_time < 0:\n            return 1 if family_size < 15 else 2\n        return run_time\n\n    def compute_lag_ratio(self, family_size: int, lag_ratio: float):\n        if lag_ratio < 0:\n            return min(4.0 / (family_size + 1.0), 0.2)\n        return lag_ratio\n\n\nclass ShowIncreasingSubsets(Animation):\n    def __init__(\n        self,\n        group: Mobject,\n        int_func: Callable[[float], float] = np.round,\n        suspend_mobject_updating: bool = False,\n        **kwargs\n    ):\n        self.all_submobs = list(group.submobjects)\n        self.int_func = int_func\n        super().__init__(\n            group,\n            suspend_mobject_updating=suspend_mobject_updating,\n            **kwargs\n        )\n\n    def interpolate_mobject(self, alpha: float) -> None:\n        n_submobs = len(self.all_submobs)\n        alpha = self.rate_func(alpha)\n        index = int(self.int_func(alpha * n_submobs))\n        self.update_submobject_list(index)\n\n    def update_submobject_list(self, index: int) -> None:\n        self.mobject.set_submobjects(self.all_submobs[:index])\n\n\nclass ShowSubmobjectsOneByOne(ShowIncreasingSubsets):\n    def __init__(\n        self,\n        group: Mobject,\n        int_func: Callable[[float], float] = np.ceil,\n        **kwargs\n    ):\n        super().__init__(group, int_func=int_func, **kwargs)\n\n    def update_submobject_list(self, index: int) -> None:\n        index = int(clip(index, 0, len(self.all_submobs) - 1))\n        if index == 0:\n            self.mobject.set_submobjects([])\n        else:\n            self.mobject.set_submobjects([self.all_submobs[index - 1]])\n\n\nclass AddTextWordByWord(ShowIncreasingSubsets):\n    def __init__(\n        self,\n        string_mobject: StringMobject,\n        time_per_word: float = 0.2,\n        run_time: float = -1.0, # If negative, it will be recomputed with time_per_word\n        rate_func: Callable[[float], float] = linear,\n        **kwargs\n    ):\n        assert isinstance(string_mobject, StringMobject)\n        grouped_mobject = string_mobject.build_groups()\n        if run_time < 0:\n            run_time = time_per_word * len(grouped_mobject)\n        super().__init__(\n            grouped_mobject,\n            run_time=run_time,\n            rate_func=rate_func,\n            **kwargs\n        )\n        self.string_mobject = string_mobject\n\n    def clean_up_from_scene(self, scene: Scene) -> None:\n        scene.remove(self.mobject)\n        if not self.is_remover():\n            scene.add(self.string_mobject)\n"
  },
  {
    "path": "manimlib/animation/fading.py",
    "content": "from __future__ import annotations\n\nimport numpy as np\n\nfrom manimlib.animation.animation import Animation\nfrom manimlib.animation.transform import Transform\nfrom manimlib.constants import ORIGIN\nfrom manimlib.mobject.types.vectorized_mobject import VMobject\nfrom manimlib.mobject.mobject import Group\nfrom manimlib.utils.bezier import interpolate\nfrom manimlib.utils.rate_functions import there_and_back\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable\n    from manimlib.mobject.mobject import Mobject\n    from manimlib.scene.scene import Scene\n    from manimlib.typing import Vect3\n\n\nclass Fade(Transform):\n    def __init__(\n        self,\n        mobject: Mobject,\n        shift: np.ndarray = ORIGIN,\n        scale: float = 1,\n        **kwargs\n    ):\n        self.shift_vect = shift\n        self.scale_factor = scale\n        super().__init__(mobject, **kwargs)\n\n\nclass FadeIn(Fade):\n    def create_target(self) -> Mobject:\n        return self.mobject.copy()\n\n    def create_starting_mobject(self) -> Mobject:\n        start = super().create_starting_mobject()\n        start.set_opacity(0)\n        start.scale(1.0 / self.scale_factor)\n        start.shift(-self.shift_vect)\n        return start\n\n\nclass FadeOut(Fade):\n    def __init__(\n        self,\n        mobject: Mobject,\n        shift: Vect3 = ORIGIN,\n        remover: bool = True,\n        final_alpha_value: float = 0.0,  # Put it back in original state when done,\n        **kwargs\n    ):\n        super().__init__(\n            mobject, shift,\n            remover=remover,\n            final_alpha_value=final_alpha_value,\n            **kwargs\n        )\n\n    def create_target(self) -> Mobject:\n        result = self.mobject.copy()\n        result.set_opacity(0)\n        result.shift(self.shift_vect)\n        result.scale(self.scale_factor)\n        return result\n\n\nclass FadeInFromPoint(FadeIn):\n    def __init__(self, mobject: Mobject, point: Vect3, **kwargs):\n        super().__init__(\n            mobject,\n            shift=mobject.get_center() - point,\n            scale=np.inf,\n            **kwargs,\n        )\n\n\nclass FadeOutToPoint(FadeOut):\n    def __init__(self, mobject: Mobject, point: Vect3, **kwargs):\n        super().__init__(\n            mobject,\n            shift=point - mobject.get_center(),\n            scale=0,\n            **kwargs,\n        )\n\n\nclass FadeTransform(Transform):\n    def __init__(\n        self,\n        mobject: Mobject,\n        target_mobject: Mobject,\n        stretch: bool = True,\n        dim_to_match: int = 1,\n        **kwargs\n    ):\n        self.to_add_on_completion = target_mobject\n        self.stretch = stretch\n        self.dim_to_match = dim_to_match\n\n        mobject.save_state()\n        super().__init__(Group(mobject, target_mobject.copy()), **kwargs)\n\n    def begin(self) -> None:\n        self.ending_mobject = self.mobject.copy()\n        Animation.begin(self)\n        # Both 'start' and 'end' consists of the source and target mobjects.\n        # At the start, the traget should be faded replacing the source,\n        # and at the end it should be the other way around.\n        start, end = self.starting_mobject, self.ending_mobject\n        for m0, m1 in ((start[1], start[0]), (end[0], end[1])):\n            self.ghost_to(m0, m1)\n\n    def ghost_to(self, source: Mobject, target: Mobject) -> None:\n        source.replace(target, stretch=self.stretch, dim_to_match=self.dim_to_match)\n        source.set_uniform(**target.get_uniforms())\n        source.set_opacity(0)\n\n    def get_all_mobjects(self) -> list[Mobject]:\n        return [\n            self.mobject,\n            self.starting_mobject,\n            self.ending_mobject,\n        ]\n\n    def get_all_families_zipped(self) -> zip[tuple[Mobject]]:\n        return Animation.get_all_families_zipped(self)\n\n    def clean_up_from_scene(self, scene: Scene) -> None:\n        Animation.clean_up_from_scene(self, scene)\n        scene.remove(self.mobject)\n        self.mobject[0].restore()\n        if not self.remover:\n            scene.add(self.to_add_on_completion)\n\n\nclass FadeTransformPieces(FadeTransform):\n    def begin(self) -> None:\n        self.mobject[0].align_family(self.mobject[1])\n        super().begin()\n\n    def ghost_to(self, source: Mobject, target: Mobject) -> None:\n        for sm0, sm1 in zip(source.get_family(), target.get_family()):\n            super().ghost_to(sm0, sm1)\n\n\nclass VFadeIn(Animation):\n    \"\"\"\n    VFadeIn and VFadeOut only work for VMobjects,\n    \"\"\"\n    def __init__(self, vmobject: VMobject, suspend_mobject_updating: bool = False, **kwargs):\n        super().__init__(\n            vmobject,\n            suspend_mobject_updating=suspend_mobject_updating,\n            **kwargs\n        )\n\n    def interpolate_submobject(\n        self,\n        submob: VMobject,\n        start: VMobject,\n        alpha: float\n    ) -> None:\n        submob.set_stroke(\n            opacity=interpolate(0, start.get_stroke_opacity(), alpha)\n        )\n        submob.set_fill(\n            opacity=interpolate(0, start.get_fill_opacity(), alpha)\n        )\n\n\nclass VFadeOut(VFadeIn):\n    def __init__(\n        self,\n        vmobject: VMobject,\n        remover: bool = True,\n        final_alpha_value: float = 0.0,\n        **kwargs\n    ):\n        super().__init__(\n            vmobject,\n            remover=remover,\n            final_alpha_value=final_alpha_value,\n            **kwargs\n        )\n\n    def interpolate_submobject(\n        self,\n        submob: VMobject,\n        start: VMobject,\n        alpha: float\n    ) -> None:\n        super().interpolate_submobject(submob, start, 1 - alpha)\n\n\nclass VFadeInThenOut(VFadeIn):\n    def __init__(\n        self,\n        vmobject: VMobject,\n        rate_func: Callable[[float], float] = there_and_back,\n        remover: bool = True,\n        final_alpha_value: float = 0.5,\n        **kwargs\n    ):\n        super().__init__(\n            vmobject,\n            rate_func=rate_func,\n            remover=remover,\n            final_alpha_value=final_alpha_value,\n            **kwargs\n        )\n"
  },
  {
    "path": "manimlib/animation/growing.py",
    "content": "from __future__ import annotations\n\nfrom manimlib.animation.transform import Transform\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    import numpy as np\n\n    from manimlib.mobject.geometry import Arrow\n    from manimlib.mobject.mobject import Mobject\n    from manimlib.typing import ManimColor\n\n\nclass GrowFromPoint(Transform):\n    def __init__(\n        self,\n        mobject: Mobject,\n        point: np.ndarray,\n        point_color: ManimColor = None,\n        **kwargs\n    ):\n        self.point = point\n        self.point_color = point_color\n        super().__init__(mobject, **kwargs)\n\n    def create_target(self) -> Mobject:\n        return self.mobject.copy()\n\n    def create_starting_mobject(self) -> Mobject:\n        start = super().create_starting_mobject()\n        start.scale(0)\n        start.move_to(self.point)\n        if self.point_color is not None:\n            start.set_color(self.point_color)\n        return start\n\n\nclass GrowFromCenter(GrowFromPoint):\n    def __init__(self, mobject: Mobject, **kwargs):\n        point = mobject.get_center()\n        super().__init__(mobject, point, **kwargs)\n\n\nclass GrowFromEdge(GrowFromPoint):\n    def __init__(self, mobject: Mobject, edge: np.ndarray, **kwargs):\n        point = mobject.get_bounding_box_point(edge)\n        super().__init__(mobject, point, **kwargs)\n\n\nclass GrowArrow(GrowFromPoint):\n    def __init__(self, arrow: Arrow, **kwargs):\n        point = arrow.get_start()\n        super().__init__(arrow, point, **kwargs)\n"
  },
  {
    "path": "manimlib/animation/indication.py",
    "content": "from __future__ import annotations\n\nimport numpy as np\n\nfrom manimlib.animation.animation import Animation\nfrom manimlib.animation.composition import AnimationGroup\nfrom manimlib.animation.composition import Succession\nfrom manimlib.animation.creation import ShowCreation\nfrom manimlib.animation.creation import ShowPartial\nfrom manimlib.animation.fading import FadeOut\nfrom manimlib.animation.fading import FadeIn\nfrom manimlib.animation.movement import Homotopy\nfrom manimlib.animation.transform import Transform\nfrom manimlib.constants import FRAME_X_RADIUS, FRAME_Y_RADIUS\nfrom manimlib.constants import ORIGIN, RIGHT, UP\nfrom manimlib.constants import SMALL_BUFF\nfrom manimlib.constants import DEG\nfrom manimlib.constants import TAU\nfrom manimlib.constants import GREY, YELLOW\nfrom manimlib.mobject.geometry import Circle\nfrom manimlib.mobject.geometry import Dot\nfrom manimlib.mobject.geometry import Line\nfrom manimlib.mobject.shape_matchers import SurroundingRectangle\nfrom manimlib.mobject.shape_matchers import Underline\nfrom manimlib.mobject.types.vectorized_mobject import VMobject\nfrom manimlib.mobject.types.vectorized_mobject import VGroup\nfrom manimlib.utils.bezier import interpolate\nfrom manimlib.utils.rate_functions import smooth\nfrom manimlib.utils.rate_functions import squish_rate_func\nfrom manimlib.utils.rate_functions import there_and_back\nfrom manimlib.utils.rate_functions import wiggle\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable\n    from manimlib.typing import ManimColor\n    from manimlib.mobject.mobject import Mobject\n\n\nclass FocusOn(Transform):\n    def __init__(\n        self,\n        focus_point: np.ndarray | Mobject,\n        opacity: float = 0.2,\n        color: ManimColor = GREY,\n        run_time: float = 2,\n        remover: bool = True,\n        **kwargs\n    ):\n        self.focus_point = focus_point\n        self.opacity = opacity\n        self.color = color\n        # Initialize with blank mobject, while create_target\n        # and create_starting_mobject handle the meat\n        super().__init__(VMobject(), run_time=run_time, remover=remover, **kwargs)\n\n    def create_target(self) -> Dot:\n        little_dot = Dot(radius=0)\n        little_dot.set_fill(self.color, opacity=self.opacity)\n        little_dot.add_updater(lambda d: d.move_to(self.focus_point))\n        return little_dot\n\n    def create_starting_mobject(self) -> Dot:\n        return Dot(\n            radius=FRAME_X_RADIUS + FRAME_Y_RADIUS,\n            stroke_width=0,\n            fill_color=self.color,\n            fill_opacity=0,\n        )\n\n\nclass Indicate(Transform):\n    def __init__(\n        self,\n        mobject: Mobject,\n        scale_factor: float = 1.2,\n        color: ManimColor = YELLOW,\n        rate_func: Callable[[float], float] = there_and_back,\n        **kwargs\n    ):\n        self.scale_factor = scale_factor\n        self.color = color\n        super().__init__(mobject, rate_func=rate_func, **kwargs)\n\n    def create_target(self) -> Mobject:\n        target = self.mobject.copy()\n        target.scale(self.scale_factor)\n        target.set_color(self.color)\n        return target\n\n\nclass Flash(AnimationGroup):\n    def __init__(\n        self,\n        point: np.ndarray | Mobject,\n        color: ManimColor = YELLOW,\n        line_length: float = 0.2,\n        num_lines: int = 12,\n        flash_radius: float = 0.3,\n        line_stroke_width: float = 3.0,\n        run_time: float = 1.0,\n        **kwargs\n    ):\n        self.point = point\n        self.color = color\n        self.line_length = line_length\n        self.num_lines = num_lines\n        self.flash_radius = flash_radius\n        self.line_stroke_width = line_stroke_width\n\n        self.lines = self.create_lines()\n        animations = self.create_line_anims()\n        super().__init__(\n            *animations,\n            group=self.lines,\n            run_time=run_time,\n            **kwargs,\n        )\n\n    def create_lines(self) -> VGroup:\n        lines = VGroup()\n        for angle in np.arange(0, TAU, TAU / self.num_lines):\n            line = Line(ORIGIN, self.line_length * RIGHT)\n            line.shift((self.flash_radius - self.line_length) * RIGHT)\n            line.rotate(angle, about_point=ORIGIN)\n            lines.add(line)\n        lines.set_stroke(\n            color=self.color,\n            width=self.line_stroke_width\n        )\n        lines.add_updater(lambda l: l.move_to(self.point))\n        return lines\n\n    def create_line_anims(self) -> list[Animation]:\n        return [\n            ShowCreationThenDestruction(line)\n            for line in self.lines\n        ]\n\n\nclass CircleIndicate(Transform):\n    def __init__(\n        self,\n        mobject: Mobject,\n        scale_factor: float = 1.2,\n        rate_func: Callable[[float], float] = there_and_back,\n        stroke_color: ManimColor = YELLOW,\n        stroke_width: float = 3.0,\n        remover: bool = True,\n        **kwargs\n    ):\n        circle = Circle(stroke_color=stroke_color, stroke_width=stroke_width)\n        circle.surround(mobject)\n        pre_circle = circle.copy().set_stroke(width=0)\n        pre_circle.scale(1 / scale_factor)\n        super().__init__(\n            pre_circle, circle,\n            rate_func=rate_func,\n            remover=remover,\n            **kwargs\n        )\n\n\nclass ShowPassingFlash(ShowPartial):\n    def __init__(\n        self,\n        mobject: Mobject,\n        time_width: float = 0.1,\n        remover: bool = True,\n        **kwargs\n    ):\n        self.time_width = time_width\n        super().__init__(\n            mobject,\n            remover=remover,\n            **kwargs\n        )\n\n    def get_bounds(self, alpha: float) -> tuple[float, float]:\n        tw = self.time_width\n        upper = interpolate(0, 1 + tw, alpha)\n        lower = upper - tw\n        upper = min(upper, 1)\n        lower = max(lower, 0)\n        return (lower, upper)\n\n    def finish(self) -> None:\n        super().finish()\n        for submob, start in self.get_all_families_zipped():\n            submob.pointwise_become_partial(start, 0, 1)\n\n\nclass VShowPassingFlash(Animation):\n    def __init__(\n        self,\n        vmobject: VMobject,\n        time_width: float = 0.3,\n        taper_width: float = 0.05,\n        remover: bool = True,\n        **kwargs\n    ):\n        self.time_width = time_width\n        self.taper_width = taper_width\n        super().__init__(vmobject, remover=remover, **kwargs)\n        self.mobject = vmobject\n\n    def taper_kernel(self, x):\n        if x < self.taper_width:\n            return x\n        elif x > 1 - self.taper_width:\n            return 1.0 - x\n        return 1.0\n\n    def begin(self) -> None:\n        # Compute an array of stroke widths for each submobject\n        # which tapers out at either end\n        self.submob_to_widths = dict()\n        for sm in self.mobject.get_family():\n            widths = sm.get_stroke_widths()\n            self.submob_to_widths[hash(sm)] = np.array([\n                width * self.taper_kernel(x)\n                for width, x in zip(widths, np.linspace(0, 1, len(widths)))\n            ])\n        super().begin()\n\n    def interpolate_submobject(\n        self,\n        submobject: VMobject,\n        starting_sumobject: None,\n        alpha: float\n    ) -> None:\n        widths = self.submob_to_widths[hash(submobject)]\n\n        # Create a gaussian such that 3 sigmas out on either side\n        # will equals time_width\n        tw = self.time_width\n        sigma = tw / 6\n        mu = interpolate(-tw / 2, 1 + tw / 2, alpha)\n        xs = np.linspace(0, 1, len(widths))\n        zs = (xs - mu) / sigma\n        gaussian = np.exp(-0.5 * zs * zs)\n        gaussian[abs(xs - mu) > 3 * sigma] = 0\n\n        if len(widths * gaussian) !=0:\n            submobject.set_stroke(width=widths * gaussian)\n\n\n    def finish(self) -> None:\n        super().finish()\n        for submob, start in self.get_all_families_zipped():\n            submob.match_style(start)\n\n\nclass FlashAround(VShowPassingFlash):\n    def __init__(\n        self,\n        mobject: Mobject,\n        time_width: float = 1.0,\n        taper_width: float = 0.0,\n        stroke_width: float = 4.0,\n        color: ManimColor = YELLOW,\n        buff: float = SMALL_BUFF,\n        n_inserted_curves: int = 100,\n        **kwargs\n    ):\n        path = self.get_path(mobject, buff)\n        if mobject.is_fixed_in_frame():\n            path.fix_in_frame()\n        path.insert_n_curves(n_inserted_curves)\n        path.set_points(path.get_points_without_null_curves())\n        path.set_stroke(color, stroke_width)\n        super().__init__(path, time_width=time_width, taper_width=taper_width, **kwargs)\n\n    def get_path(self, mobject: Mobject, buff: float) -> SurroundingRectangle:\n        return SurroundingRectangle(mobject, buff=buff)\n\n\nclass FlashUnder(FlashAround):\n    def get_path(self, mobject: Mobject, buff: float) -> Underline:\n        return Underline(mobject, buff=buff, stretch_factor=1.0)\n\n\nclass ShowCreationThenDestruction(ShowPassingFlash):\n    def __init__(self, vmobject: VMobject, time_width: float = 2.0, **kwargs):\n        super().__init__(vmobject, time_width=time_width, **kwargs)\n\n\nclass ShowCreationThenFadeOut(Succession):\n    def __init__(self, mobject: Mobject, remover: bool = True, **kwargs):\n        super().__init__(\n            ShowCreation(mobject),\n            FadeOut(mobject),\n            remover=remover,\n            **kwargs\n        )\n\n\nclass AnimationOnSurroundingRectangle(AnimationGroup):\n    RectAnimationType: type = Animation\n\n    def __init__(\n        self,\n        mobject: Mobject,\n        stroke_width: float = 2.0,\n        stroke_color: ManimColor = YELLOW,\n        buff: float = SMALL_BUFF,\n        **kwargs\n    ):\n        rect = SurroundingRectangle(\n            mobject,\n            stroke_width=stroke_width,\n            stroke_color=stroke_color,\n            buff=buff,\n        )\n        rect.add_updater(lambda r: r.move_to(mobject))\n        super().__init__(self.RectAnimationType(rect, **kwargs))\n\n\nclass ShowPassingFlashAround(AnimationOnSurroundingRectangle):\n    RectAnimationType = ShowPassingFlash\n\n\nclass ShowCreationThenDestructionAround(AnimationOnSurroundingRectangle):\n    RectAnimationType = ShowCreationThenDestruction\n\n\nclass ShowCreationThenFadeAround(AnimationOnSurroundingRectangle):\n    RectAnimationType = ShowCreationThenFadeOut\n\n\nclass ApplyWave(Homotopy):\n    def __init__(\n        self,\n        mobject: Mobject,\n        direction: np.ndarray = UP,\n        amplitude: float = 0.2,\n        run_time: float = 1.0,\n        **kwargs\n    ):\n\n        left_x = mobject.get_left()[0]\n        right_x = mobject.get_right()[0]\n        vect = amplitude * direction\n\n        def homotopy(x, y, z, t):\n            alpha = (x - left_x) / (right_x - left_x)\n            power = np.exp(2.0 * (alpha - 0.5))\n            nudge = there_and_back(t**power)\n            return np.array([x, y, z]) + nudge * vect\n\n        super().__init__(homotopy, mobject, **kwargs)\n\n\nclass WiggleOutThenIn(Animation):\n    def __init__(\n        self,\n        mobject: Mobject,\n        scale_value: float = 1.1,\n        rotation_angle: float = 0.01 * TAU,\n        n_wiggles: int = 6,\n        scale_about_point: np.ndarray | None = None,\n        rotate_about_point: np.ndarray | None = None,\n        run_time: float = 2,\n        **kwargs\n    ):\n        self.scale_value = scale_value\n        self.rotation_angle = rotation_angle\n        self.n_wiggles = n_wiggles\n        self.scale_about_point = scale_about_point\n        self.rotate_about_point = rotate_about_point\n        super().__init__(mobject, run_time=run_time, **kwargs)\n\n    def get_scale_about_point(self) -> np.ndarray:\n        return self.scale_about_point or self.mobject.get_center()\n\n    def get_rotate_about_point(self) -> np.ndarray:\n        return self.rotate_about_point or self.mobject.get_center()\n\n    def interpolate_submobject(\n        self,\n        submobject: Mobject,\n        starting_sumobject: Mobject,\n        alpha: float\n    ) -> None:\n        submobject.match_points(starting_sumobject)\n        submobject.scale(\n            interpolate(1, self.scale_value, there_and_back(alpha)),\n            about_point=self.get_scale_about_point()\n        )\n        submobject.rotate(\n            wiggle(alpha, self.n_wiggles) * self.rotation_angle,\n            about_point=self.get_rotate_about_point()\n        )\n\n\nclass TurnInsideOut(Transform):\n    def __init__(self, mobject: Mobject, path_arc: float = 90 * DEG, **kwargs):\n        super().__init__(mobject, path_arc=path_arc, **kwargs)\n\n    def create_target(self) -> Mobject:\n        result = self.mobject.copy().reverse_points()\n        if isinstance(result, VMobject):\n            result.refresh_triangulation()\n        return result\n\n\nclass FlashyFadeIn(AnimationGroup):\n    def __init__(self,\n        vmobject: VMobject,\n        stroke_width: float = 2.0,\n        fade_lag: float = 0.0,\n        time_width: float = 1.0,\n        **kwargs\n    ):\n        outline = vmobject.copy()\n        outline.set_fill(opacity=0)\n        outline.set_stroke(width=stroke_width, opacity=1)\n\n        rate_func = kwargs.get(\"rate_func\", smooth)\n        super().__init__(\n            FadeIn(vmobject, rate_func=squish_rate_func(rate_func, fade_lag, 1)),\n            VShowPassingFlash(outline, time_width=time_width),\n            **kwargs\n        )\n"
  },
  {
    "path": "manimlib/animation/movement.py",
    "content": "from __future__ import annotations\n\nfrom manimlib.animation.animation import Animation\nfrom manimlib.utils.rate_functions import linear\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable, Sequence\n\n    import numpy as np\n\n    from manimlib.mobject.mobject import Mobject\n    from manimlib.mobject.types.vectorized_mobject import VMobject\n\n\nclass Homotopy(Animation):\n    apply_function_config: dict = dict()\n\n    def __init__(\n        self,\n        homotopy: Callable[[float, float, float, float], Sequence[float]],\n        mobject: Mobject,\n        run_time: float = 3.0,\n        **kwargs\n    ):\n        \"\"\"\n        Homotopy is a function from\n        (x, y, z, t) to (x', y', z')\n        \"\"\"\n        self.homotopy = homotopy\n        super().__init__(mobject, run_time=run_time, **kwargs)\n\n    def function_at_time_t(self, t: float) -> Callable[[np.ndarray], Sequence[float]]:\n        def result(p):\n            return self.homotopy(*p, t)\n        return result\n\n    def interpolate_submobject(\n        self,\n        submob: Mobject,\n        start: Mobject,\n        alpha: float\n    ) -> None:\n        submob.match_points(start)\n        submob.apply_function(\n            self.function_at_time_t(alpha),\n            **self.apply_function_config\n        )\n\n\nclass SmoothedVectorizedHomotopy(Homotopy):\n    apply_function_config: dict = dict(make_smooth=True)\n\n\nclass ComplexHomotopy(Homotopy):\n    def __init__(\n        self,\n        complex_homotopy: Callable[[complex, float], complex],\n        mobject: Mobject,\n        **kwargs\n    ):\n        \"\"\"\n        Given a function form (z, t) -> w, where z and w\n        are complex numbers and t is time, this animates\n        the state over time\n        \"\"\"\n        def homotopy(x, y, z, t):\n            c = complex_homotopy(complex(x, y), t)\n            return (c.real, c.imag, z)\n\n        super().__init__(homotopy, mobject, **kwargs)\n\n\nclass PhaseFlow(Animation):\n    def __init__(\n        self,\n        function: Callable[[np.ndarray], np.ndarray],\n        mobject: Mobject,\n        virtual_time: float | None = None,\n        suspend_mobject_updating: bool = False,\n        rate_func: Callable[[float], float] = linear,\n        run_time: float =3.0,\n        **kwargs\n    ):\n        self.function = function\n        self.virtual_time = virtual_time or run_time\n        super().__init__(\n            mobject,\n            rate_func=rate_func,\n            run_time=run_time,\n            suspend_mobject_updating=suspend_mobject_updating,\n            **kwargs\n        )\n\n    def interpolate_mobject(self, alpha: float) -> None:\n        if hasattr(self, \"last_alpha\"):\n            dt = self.virtual_time * (alpha - self.last_alpha)\n            self.mobject.apply_function(\n                lambda p: p + dt * self.function(p)\n            )\n        self.last_alpha = alpha\n\n\nclass MoveAlongPath(Animation):\n    def __init__(\n        self,\n        mobject: Mobject,\n        path: VMobject,\n        suspend_mobject_updating: bool = False,\n        **kwargs\n    ):\n        self.path = path\n        super().__init__(mobject, suspend_mobject_updating=suspend_mobject_updating, **kwargs)\n\n    def interpolate_mobject(self, alpha: float) -> None:\n        point = self.path.quick_point_from_proportion(self.rate_func(alpha))\n        self.mobject.move_to(point)\n"
  },
  {
    "path": "manimlib/animation/numbers.py",
    "content": "from __future__ import annotations\n\nfrom manimlib.animation.animation import Animation\nfrom manimlib.mobject.numbers import DecimalNumber\nfrom manimlib.utils.bezier import interpolate\nfrom manimlib.utils.simple_functions import clip\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable\n\n\nclass ChangingDecimal(Animation):\n    def __init__(\n        self,\n        decimal_mob: DecimalNumber,\n        number_update_func: Callable[[float], float],\n        suspend_mobject_updating: bool = False,\n        **kwargs\n    ):\n        assert isinstance(decimal_mob, DecimalNumber)\n        self.number_update_func = number_update_func\n        super().__init__(\n            decimal_mob,\n            suspend_mobject_updating=suspend_mobject_updating,\n            **kwargs\n        )\n        self.mobject = decimal_mob\n\n    def interpolate_mobject(self, alpha: float) -> None:\n        true_alpha = self.time_spanned_alpha(alpha)\n        new_value = self.number_update_func(true_alpha)\n        self.mobject.set_value(new_value)\n\n\nclass ChangeDecimalToValue(ChangingDecimal):\n    def __init__(\n        self,\n        decimal_mob: DecimalNumber,\n        target_number: float | complex,\n        **kwargs\n    ):\n        start_number = decimal_mob.number\n        super().__init__(\n            decimal_mob,\n            lambda a: interpolate(start_number, target_number, a),\n            **kwargs\n        )\n\n\nclass CountInFrom(ChangingDecimal):\n    def __init__(\n        self,\n        decimal_mob: DecimalNumber,\n        source_number: float | complex = 0,\n        **kwargs\n    ):\n        start_number = decimal_mob.get_value()\n        super().__init__(\n            decimal_mob,\n            lambda a: interpolate(source_number, start_number, clip(a, 0, 1)),\n            **kwargs\n        )\n"
  },
  {
    "path": "manimlib/animation/rotation.py",
    "content": "from __future__ import annotations\n\nfrom manimlib.animation.animation import Animation\nfrom manimlib.constants import ORIGIN, OUT\nfrom manimlib.constants import PI, TAU\nfrom manimlib.utils.rate_functions import linear\nfrom manimlib.utils.rate_functions import smooth\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    import numpy as np\n    from typing import Callable\n    from manimlib.mobject.mobject import Mobject\n\n\nclass Rotating(Animation):\n    def __init__(\n        self,\n        mobject: Mobject,\n        angle: float = TAU,\n        axis: np.ndarray = OUT,\n        about_point: np.ndarray | None = None,\n        about_edge: np.ndarray | None = None,\n        run_time: float = 5.0,\n        rate_func: Callable[[float], float] = linear,\n        suspend_mobject_updating: bool = False,\n        **kwargs\n    ):\n        self.angle = angle\n        self.axis = axis\n        self.about_point = about_point\n        self.about_edge = about_edge\n        super().__init__(\n            mobject,\n            run_time=run_time,\n            rate_func=rate_func,\n            suspend_mobject_updating=suspend_mobject_updating,\n            **kwargs\n        )\n\n    def interpolate_mobject(self, alpha: float) -> None:\n        pairs = zip(\n            self.mobject.family_members_with_points(),\n            self.starting_mobject.family_members_with_points(),\n        )\n        for sm1, sm2 in pairs:\n            for key in sm1.pointlike_data_keys:\n                sm1.data[key][:] = sm2.data[key]\n        self.mobject.rotate(\n            self.rate_func(self.time_spanned_alpha(alpha)) * self.angle,\n            axis=self.axis,\n            about_point=self.about_point,\n            about_edge=self.about_edge,\n        )\n\n\nclass Rotate(Rotating):\n    def __init__(\n        self,\n        mobject: Mobject,\n        angle: float = PI,\n        axis: np.ndarray = OUT,\n        run_time: float = 1,\n        rate_func: Callable[[float], float] = smooth,\n        about_edge: np.ndarray = ORIGIN,\n        **kwargs\n    ):\n        super().__init__(\n            mobject, angle, axis,\n            run_time=run_time,\n            rate_func=rate_func,\n            about_edge=about_edge,\n            **kwargs\n        )\n"
  },
  {
    "path": "manimlib/animation/specialized.py",
    "content": "from __future__ import annotations\n\nfrom manimlib.animation.composition import LaggedStart\nfrom manimlib.animation.transform import Restore\nfrom manimlib.constants import BLACK, WHITE\nfrom manimlib.mobject.geometry import Circle\nfrom manimlib.mobject.types.vectorized_mobject import VGroup\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    import numpy as np\n    from manimlib.typing import ManimColor\n\n\nclass Broadcast(LaggedStart):\n    def __init__(\n        self,\n        focal_point: np.ndarray,\n        small_radius: float = 0.0,\n        big_radius: float = 5.0,\n        n_circles: int = 5,\n        start_stroke_width: float = 8.0,\n        color: ManimColor = WHITE,\n        run_time: float = 3.0,\n        lag_ratio: float = 0.2,\n        remover: bool = True,\n        **kwargs\n    ):\n        self.focal_point = focal_point\n        self.small_radius = small_radius\n        self.big_radius = big_radius\n        self.n_circles = n_circles\n        self.start_stroke_width = start_stroke_width\n        self.color = color\n\n        circles = VGroup()\n        for x in range(n_circles):\n            circle = Circle(\n                radius=big_radius,\n                stroke_color=BLACK,\n                stroke_width=0,\n            )\n            circle.add_updater(lambda c: c.move_to(focal_point))\n            circle.save_state()\n            circle.set_width(small_radius * 2)\n            circle.set_stroke(color, start_stroke_width)\n            circles.add(circle)\n        super().__init__(\n            *map(Restore, circles),\n            run_time=run_time,\n            lag_ratio=lag_ratio,\n            remover=remover,\n            **kwargs\n        )\n"
  },
  {
    "path": "manimlib/animation/transform.py",
    "content": "from __future__ import annotations\n\nimport inspect\n\nimport numpy as np\n\nfrom manimlib.animation.animation import Animation\nfrom manimlib.constants import DEG\nfrom manimlib.constants import OUT\nfrom manimlib.mobject.mobject import Group\nfrom manimlib.mobject.mobject import Mobject\nfrom manimlib.utils.paths import path_along_arc\nfrom manimlib.utils.paths import straight_path\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable\n    import numpy.typing as npt\n    from manimlib.scene.scene import Scene\n    from manimlib.typing import ManimColor\n\n\nclass Transform(Animation):\n    replace_mobject_with_target_in_scene: bool = False\n\n    def __init__(\n        self,\n        mobject: Mobject,\n        target_mobject: Mobject | None = None,\n        path_arc: float | Tuple[float, float] = 0.0,\n        path_arc_axis: np.ndarray = OUT,\n        path_func: Callable | None = None,\n        **kwargs\n    ):\n        self.target_mobject = target_mobject\n        self.path_arc = path_arc\n        self.path_arc_axis = path_arc_axis\n        self.path_func = path_func\n        super().__init__(mobject, **kwargs)\n        self.init_path_func()\n\n    def init_path_func(self) -> None:\n        if self.path_func is not None:\n            return\n        elif isinstance(self.path_arc, float) and self.path_arc == 0:\n            self.path_func = straight_path\n        else:\n            self.path_func = path_along_arc(\n                self.path_arc,\n                self.path_arc_axis,\n            )\n\n    def begin(self) -> None:\n        self.target_mobject = self.create_target()\n        self.check_target_mobject_validity()\n\n        if self.mobject.is_aligned_with(self.target_mobject):\n            self.target_copy = self.target_mobject\n        else:\n            # Use a copy of target_mobject for the align_data_and_family\n            # call so that the actual target_mobject stays\n            # preserved, since calling align_data will potentially\n            # change the structure of both arguments\n            self.target_copy = self.target_mobject.copy()\n        self.mobject.align_data_and_family(self.target_copy)\n        super().begin()\n        if not self.mobject.has_updaters():\n            self.mobject.lock_matching_data(\n                self.starting_mobject,\n                self.target_copy,\n            )\n\n    def finish(self) -> None:\n        super().finish()\n        self.mobject.unlock_data()\n\n    def create_target(self) -> Mobject:\n        # Has no meaningful effect here, but may be useful\n        # in subclasses\n        return self.target_mobject\n\n    def check_target_mobject_validity(self) -> None:\n        if self.target_mobject is None:\n            raise Exception(\n                f\"{self.__class__.__name__}.create_target not properly implemented\"\n            )\n\n    def clean_up_from_scene(self, scene: Scene) -> None:\n        super().clean_up_from_scene(scene)\n        if self.replace_mobject_with_target_in_scene:\n            scene.remove(self.mobject)\n            scene.add(self.target_mobject)\n\n    def update_config(self, **kwargs) -> None:\n        Animation.update_config(self, **kwargs)\n        if \"path_arc\" in kwargs:\n            self.path_func = path_along_arc(\n                kwargs[\"path_arc\"],\n                kwargs.get(\"path_arc_axis\", OUT)\n            )\n\n    def get_all_mobjects(self) -> list[Mobject]:\n        return [\n            self.mobject,\n            self.starting_mobject,\n            self.target_mobject,\n            self.target_copy,\n        ]\n\n    def get_all_families_zipped(self) -> zip[tuple[Mobject]]:\n        return zip(*[\n            mob.get_family()\n            for mob in [\n                self.mobject,\n                self.starting_mobject,\n                self.target_copy,\n            ]\n        ])\n\n    def interpolate_submobject(\n        self,\n        submob: Mobject,\n        start: Mobject,\n        target_copy: Mobject,\n        alpha: float\n    ):\n        submob.interpolate(start, target_copy, alpha, self.path_func)\n        return self\n\n\nclass ReplacementTransform(Transform):\n    replace_mobject_with_target_in_scene: bool = True\n\n\nclass TransformFromCopy(Transform):\n    replace_mobject_with_target_in_scene: bool = True\n\n    def __init__(self, mobject: Mobject, target_mobject: Mobject, **kwargs):\n        super().__init__(mobject.copy(), target_mobject, **kwargs)\n\n\nclass MoveToTarget(Transform):\n    def __init__(self, mobject: Mobject, **kwargs):\n        self.check_validity_of_input(mobject)\n        super().__init__(mobject, mobject.target, **kwargs)\n\n    def check_validity_of_input(self, mobject: Mobject) -> None:\n        if not hasattr(mobject, \"target\"):\n            raise Exception(\n                \"MoveToTarget called on mobject without attribute 'target'\"\n            )\n\n\nclass _MethodAnimation(MoveToTarget):\n    def __init__(self, mobject: Mobject, methods: list[Callable], **kwargs):\n        self.methods = methods\n        super().__init__(mobject, **kwargs)\n\n\nclass ApplyMethod(Transform):\n    def __init__(self, method: Callable, *args, **kwargs):\n        \"\"\"\n        method is a method of Mobject, *args are arguments for\n        that method.  Key word arguments should be passed in\n        as the last arg, as a dict, since **kwargs is for\n        configuration of the transform itself\n\n        Relies on the fact that mobject methods return the mobject\n        \"\"\"\n        self.check_validity_of_input(method)\n        self.method = method\n        self.method_args = args\n        super().__init__(method.__self__, **kwargs)\n\n    def check_validity_of_input(self, method: Callable) -> None:\n        if not inspect.ismethod(method):\n            raise Exception(\n                \"Whoops, looks like you accidentally invoked \"\n                \"the method you want to animate\"\n            )\n        assert isinstance(method.__self__, Mobject)\n\n    def create_target(self) -> Mobject:\n        method = self.method\n        # Make sure it's a list so that args.pop() works\n        args = list(self.method_args)\n\n        if len(args) > 0 and isinstance(args[-1], dict):\n            method_kwargs = args.pop()\n        else:\n            method_kwargs = {}\n        target = method.__self__.copy()\n        method.__func__(target, *args, **method_kwargs)\n        return target\n\n\nclass ApplyPointwiseFunction(ApplyMethod):\n    def __init__(\n        self,\n        function: Callable[[np.ndarray], np.ndarray],\n        mobject: Mobject,\n        run_time: float = 3.0,\n        **kwargs\n    ):\n        super().__init__(mobject.apply_function, function, run_time=run_time, **kwargs)\n\n\nclass ApplyPointwiseFunctionToCenter(Transform):\n    def __init__(\n        self,\n        function: Callable[[np.ndarray], np.ndarray],\n        mobject: Mobject,\n        **kwargs\n    ):\n        self.function = function\n        super().__init__(mobject, **kwargs)\n\n    def create_target(self) -> Mobject:\n        return self.mobject.copy().move_to(self.function(self.mobject.get_center()))\n\n\nclass FadeToColor(ApplyMethod):\n    def __init__(\n        self,\n        mobject: Mobject,\n        color: ManimColor,\n        **kwargs\n    ):\n        super().__init__(mobject.set_color, color, **kwargs)\n\n\nclass ScaleInPlace(ApplyMethod):\n    def __init__(\n        self,\n        mobject: Mobject,\n        scale_factor: npt.ArrayLike,\n        **kwargs\n    ):\n        super().__init__(mobject.scale, scale_factor, **kwargs)\n\n\nclass ShrinkToCenter(ScaleInPlace):\n    def __init__(self, mobject: Mobject, **kwargs):\n        super().__init__(mobject, 0, **kwargs)\n\n\nclass Restore(Transform):\n    def __init__(self, mobject: Mobject, **kwargs):\n        if not hasattr(mobject, \"saved_state\") or mobject.saved_state is None:\n            raise Exception(\"Trying to restore without having saved\")\n        super().__init__(mobject, mobject.saved_state, **kwargs)\n\n\nclass ApplyFunction(Transform):\n    def __init__(\n        self,\n        function: Callable[[Mobject], Mobject],\n        mobject: Mobject,\n        **kwargs\n    ):\n        self.function = function\n        super().__init__(mobject, **kwargs)\n\n    def create_target(self) -> Mobject:\n        target = self.function(self.mobject.copy())\n        if not isinstance(target, Mobject):\n            raise Exception(\"Functions passed to ApplyFunction must return object of type Mobject\")\n        return target\n\n\nclass ApplyMatrix(ApplyPointwiseFunction):\n    def __init__(\n        self,\n        matrix: npt.ArrayLike,\n        mobject: Mobject,\n        **kwargs\n    ):\n        matrix = self.initialize_matrix(matrix)\n\n        def func(p):\n            return np.dot(p, matrix.T)\n\n        super().__init__(func, mobject, **kwargs)\n\n    def initialize_matrix(self, matrix: npt.ArrayLike) -> np.ndarray:\n        matrix = np.array(matrix)\n        if matrix.shape == (2, 2):\n            new_matrix = np.identity(3)\n            new_matrix[:2, :2] = matrix\n            matrix = new_matrix\n        elif matrix.shape != (3, 3):\n            raise Exception(\"Matrix has bad dimensions\")\n        return matrix\n\n\nclass ApplyComplexFunction(ApplyMethod):\n    def __init__(\n        self,\n        function: Callable[[complex], complex],\n        mobject: Mobject,\n        **kwargs\n    ):\n        self.function = function\n        method = mobject.apply_complex_function\n        super().__init__(method, function, **kwargs)\n\n    def init_path_func(self) -> None:\n        func1 = self.function(complex(1))\n        self.path_arc = np.log(func1).imag\n        super().init_path_func()\n\n###\n\n\nclass CyclicReplace(Transform):\n    def __init__(self, *mobjects: Mobject, path_arc=90 * DEG, **kwargs):\n        super().__init__(Group(*mobjects), path_arc=path_arc, **kwargs)\n\n    def create_target(self) -> Mobject:\n        group = self.mobject\n        target = group.copy()\n        cycled_targets = [target[-1], *target[:-1]]\n        for m1, m2 in zip(cycled_targets, group):\n            m1.move_to(m2)\n        return target\n\n\nclass Swap(CyclicReplace):\n    \"\"\"Alternate name for CyclicReplace\"\"\"\n    pass\n"
  },
  {
    "path": "manimlib/animation/transform_matching_parts.py",
    "content": "from __future__ import annotations\n\nimport itertools as it\nfrom difflib import SequenceMatcher\n\nfrom manimlib.animation.composition import AnimationGroup\nfrom manimlib.animation.fading import FadeInFromPoint\nfrom manimlib.animation.fading import FadeOutToPoint\nfrom manimlib.animation.transform import Transform\nfrom manimlib.mobject.mobject import Mobject\nfrom manimlib.mobject.types.vectorized_mobject import VMobject\nfrom manimlib.mobject.svg.string_mobject import StringMobject\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Iterable\n    from manimlib.scene.scene import Scene\n\n\nclass TransformMatchingParts(AnimationGroup):\n    def __init__(\n        self,\n        source: Mobject,\n        target: Mobject,\n        matched_pairs: Iterable[tuple[Mobject, Mobject]] = [],\n        match_animation: type = Transform,\n        mismatch_animation: type = Transform,\n        run_time: float = 2,\n        lag_ratio: float = 0,\n        **kwargs,\n    ):\n        self.source = source\n        self.target = target\n        self.match_animation = match_animation\n        self.mismatch_animation = mismatch_animation\n        self.anim_config = dict(**kwargs)\n\n        # We will progressively build up a list of transforms\n        # from pieces in source to those in target. These\n        # two lists keep track of which pieces are accounted\n        # for so far\n        self.source_pieces = source.family_members_with_points()\n        self.target_pieces = target.family_members_with_points()\n        self.anims = []\n\n        for pair in matched_pairs:\n            self.add_transform(*pair)\n\n        # Match any pairs with the same shape\n        for pair in self.find_pairs_with_matching_shapes(self.source_pieces, self.target_pieces):\n            self.add_transform(*pair)\n\n        # Finally, account for mismatches\n        for source_piece in self.source_pieces:\n            if any([source_piece in anim.mobject.get_family() for anim in self.anims]):\n                continue\n            self.anims.append(FadeOutToPoint(\n                source_piece, target.get_center(),\n                **self.anim_config\n            ))\n        for target_piece in self.target_pieces:\n            if any([target_piece in anim.mobject.get_family() for anim in self.anims]):\n                continue\n            self.anims.append(FadeInFromPoint(\n                target_piece, source.get_center(),\n                **self.anim_config\n            ))\n\n        super().__init__(\n            *self.anims,\n            run_time=run_time,\n            lag_ratio=lag_ratio,\n        )\n\n    def add_transform(\n        self,\n        source: Mobject,\n        target: Mobject,\n    ):\n        new_source_pieces = source.family_members_with_points()\n        new_target_pieces = target.family_members_with_points()\n        if len(new_source_pieces) == 0 or len(new_target_pieces) == 0:\n            # Don't animate null sorces or null targets\n            return\n        source_is_new = all(char in self.source_pieces for char in new_source_pieces)\n        target_is_new = all(char in self.target_pieces for char in new_target_pieces)\n        if not source_is_new or not target_is_new:\n            return\n\n        transform_type = self.mismatch_animation\n        if source.has_same_shape_as(target):\n            transform_type = self.match_animation\n\n        self.anims.append(transform_type(source, target, **self.anim_config))\n        for char in new_source_pieces:\n            self.source_pieces.remove(char)\n        for char in new_target_pieces:\n            self.target_pieces.remove(char)\n\n    def find_pairs_with_matching_shapes(\n        self,\n        chars1: list[Mobject],\n        chars2: list[Mobject]\n    ) -> list[tuple[Mobject, Mobject]]:\n        result = []\n        for char1, char2 in it.product(chars1, chars2):\n            if char1.has_same_shape_as(char2):\n                result.append((char1, char2))\n        return result\n\n    def clean_up_from_scene(self, scene: Scene) -> None:\n        super().clean_up_from_scene(scene)\n        scene.remove(self.mobject)\n        scene.add(self.target)\n\n\nclass TransformMatchingShapes(TransformMatchingParts):\n    \"\"\"Alias for TransformMatchingParts\"\"\"\n    pass\n\n\nclass TransformMatchingStrings(TransformMatchingParts):\n    def __init__(\n        self,\n        source: StringMobject,\n        target: StringMobject,\n        matched_keys: Iterable[str] = [],\n        key_map: dict[str, str] = dict(),\n        matched_pairs: Iterable[tuple[VMobject, VMobject]] = [],\n        **kwargs,\n    ):\n        matched_pairs = [\n            *matched_pairs,\n            *self.matching_blocks(source, target, matched_keys, key_map),\n        ]\n\n        super().__init__(\n            source, target,\n            matched_pairs=matched_pairs,\n            **kwargs,\n        )\n\n    def matching_blocks(\n        self,\n        source: StringMobject,\n        target: StringMobject,\n        matched_keys: Iterable[str],\n        key_map: dict[str, str]\n    ) -> list[tuple[VMobject, VMobject]]:\n        syms1 = source.get_symbol_substrings()\n        syms2 = target.get_symbol_substrings()\n        counts1 = list(map(source.substr_to_path_count, syms1))\n        counts2 = list(map(target.substr_to_path_count, syms2))\n\n        # Start with user specified matches\n        blocks = [(source[key1], target[key2]) for key1, key2 in key_map.items()]\n        blocks += [(source[key], target[key]) for key in matched_keys]\n\n        # Nullify any intersections with those matches in the two symbol lists\n        for sub_source, sub_target in blocks:\n            for i in range(len(syms1)):\n                if i < len(source) and source[i] in sub_source.family_members_with_points():\n                    syms1[i] = \"Null1\"\n            for j in range(len(syms2)):\n                if j < len(target) and target[j] in sub_target.family_members_with_points():\n                    syms2[j] = \"Null2\"\n\n        # Group together longest matching substrings\n        while True:\n            matcher = SequenceMatcher(None, syms1, syms2)\n            match = matcher.find_longest_match(0, len(syms1), 0, len(syms2))\n            if match.size == 0:\n                break\n\n            i1 = sum(counts1[:match.a])\n            i2 = sum(counts2[:match.b])\n            size = sum(counts1[match.a:match.a + match.size])\n\n            blocks.append((source[i1:i1 + size], target[i2:i2 + size]))\n\n            for i in range(match.size):\n                syms1[match.a + i] = \"Null1\"\n                syms2[match.b + i] = \"Null2\"\n\n        return blocks\n\n\nclass TransformMatchingTex(TransformMatchingStrings):\n    \"\"\"Alias for TransformMatchingStrings\"\"\"\n    pass\n"
  },
  {
    "path": "manimlib/animation/update.py",
    "content": "from __future__ import annotations\n\nfrom manimlib.animation.animation import Animation\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable\n\n    from manimlib.mobject.mobject import Mobject\n\n\nclass UpdateFromFunc(Animation):\n    \"\"\"\n    update_function of the form func(mobject), presumably\n    to be used when the state of one mobject is dependent\n    on another simultaneously animated mobject\n    \"\"\"\n    def __init__(\n        self,\n        mobject: Mobject,\n        update_function: Callable[[Mobject], Mobject | None],\n        suspend_mobject_updating: bool = False,\n        **kwargs\n    ):\n        self.update_function = update_function\n        super().__init__(\n            mobject,\n            suspend_mobject_updating=suspend_mobject_updating,\n            **kwargs\n        )\n\n    def interpolate_mobject(self, alpha: float) -> None:\n        self.update_function(self.mobject)\n\n\nclass UpdateFromAlphaFunc(Animation):\n    def __init__(\n        self,\n        mobject: Mobject,\n        update_function: Callable[[Mobject, float], Mobject | None],\n        suspend_mobject_updating: bool = False,\n        **kwargs\n    ):\n        self.update_function = update_function\n        super().__init__(mobject, suspend_mobject_updating=suspend_mobject_updating, **kwargs)\n\n    def interpolate_mobject(self, alpha: float) -> None:\n        true_alpha = self.rate_func(self.time_spanned_alpha(alpha))\n        self.update_function(self.mobject, true_alpha)\n\n\nclass MaintainPositionRelativeTo(Animation):\n    def __init__(\n        self,\n        mobject: Mobject,\n        tracked_mobject: Mobject,\n        **kwargs\n    ):\n        self.tracked_mobject = tracked_mobject\n        self.diff = mobject.get_center() - tracked_mobject.get_center()\n        super().__init__(mobject, **kwargs)\n\n    def interpolate_mobject(self, alpha: float) -> None:\n        target = self.tracked_mobject.get_center()\n        location = self.mobject.get_center()\n        self.mobject.shift(target - location + self.diff)\n"
  },
  {
    "path": "manimlib/camera/__init__.py",
    "content": ""
  },
  {
    "path": "manimlib/camera/camera.py",
    "content": "from __future__ import annotations\n\nimport moderngl\nimport numpy as np\nimport OpenGL.GL as gl\nfrom PIL import Image\n\nfrom manimlib.camera.camera_frame import CameraFrame\nfrom manimlib.constants import BLACK\nfrom manimlib.constants import DEFAULT_RESOLUTION\nfrom manimlib.constants import FRAME_HEIGHT\nfrom manimlib.constants import FRAME_WIDTH\nfrom manimlib.mobject.mobject import Mobject\nfrom manimlib.mobject.mobject import Point\nfrom manimlib.utils.color import color_to_rgba\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Optional\n    from manimlib.typing import ManimColor, Vect3\n    from manimlib.window import Window\n\n\nclass Camera(object):\n    def __init__(\n        self,\n        window: Optional[Window] = None,\n        background_image: Optional[str] = None,\n        frame_config: dict = dict(),\n        # Note: frame height and width will be resized to match this resolution aspect ratio\n        resolution=DEFAULT_RESOLUTION,\n        fps: int = 30,\n        background_color: ManimColor = BLACK,\n        background_opacity: float = 1.0,\n        # Points in vectorized mobjects with norm greater\n        # than this value will be rescaled.\n        max_allowable_norm: float = FRAME_WIDTH,\n        image_mode: str = \"RGBA\",\n        n_channels: int = 4,\n        pixel_array_dtype: type = np.uint8,\n        light_source_position: Vect3 = np.array([-10, 10, 10]),\n        # Although vector graphics handle antialiasing fine\n        # without multisampling, for 3d scenes one might want\n        # to set samples to be greater than 0.\n        samples: int = 0,\n    ):\n        self.window = window\n        self.background_image = background_image\n        self.default_pixel_shape = resolution  # Rename?\n        self.fps = fps\n        self.max_allowable_norm = max_allowable_norm\n        self.image_mode = image_mode\n        self.n_channels = n_channels\n        self.pixel_array_dtype = pixel_array_dtype\n        self.light_source_position = light_source_position\n        self.samples = samples\n\n        self.rgb_max_val: float = np.iinfo(self.pixel_array_dtype).max\n        self.background_rgba: list[float] = list(color_to_rgba(\n            background_color, background_opacity\n        ))\n        self.uniforms = dict()\n        self.init_frame(**frame_config)\n        self.init_context()\n        self.init_fbo()\n        self.init_light_source()\n\n    def init_frame(self, **config) -> None:\n        self.frame = CameraFrame(**config)\n\n    def init_context(self) -> None:\n        if self.window is None:\n            self.ctx: moderngl.Context = moderngl.create_standalone_context()\n        else:\n            self.ctx: moderngl.Context = self.window.ctx\n\n        self.ctx.enable(moderngl.PROGRAM_POINT_SIZE)\n        self.ctx.enable(moderngl.BLEND)\n\n    def init_fbo(self) -> None:\n        # This is the buffer used when writing to a video/image file\n        self.fbo_for_files = self.get_fbo(self.samples)\n\n        # This is the frame buffer we'll draw into when emitting frames\n        self.draw_fbo = self.get_fbo(samples=0)\n\n        if self.window is None:\n            self.window_fbo = None\n            self.fbo = self.fbo_for_files\n        else:\n            self.window_fbo = self.ctx.detect_framebuffer()\n            self.fbo = self.window_fbo\n\n        self.fbo.use()\n\n    def init_light_source(self) -> None:\n        self.light_source = Point(self.light_source_position)\n\n    def use_window_fbo(self, use: bool = True):\n        assert self.window is not None\n        if use:\n            self.fbo = self.window_fbo\n        else:\n            self.fbo = self.fbo_for_files\n\n    # Methods associated with the frame buffer\n    def get_fbo(\n        self,\n        samples: int = 0\n    ) -> moderngl.Framebuffer:\n        return self.ctx.framebuffer(\n            color_attachments=self.ctx.texture(\n                self.default_pixel_shape,\n                components=self.n_channels,\n                samples=samples,\n            ),\n            depth_attachment=self.ctx.depth_renderbuffer(\n                self.default_pixel_shape,\n                samples=samples\n            )\n        )\n\n    def clear(self) -> None:\n        self.fbo.clear(*self.background_rgba)\n        if self.window:\n            self.window.clear(*self.background_rgba)\n\n    def blit(self, src_fbo, dst_fbo):\n        \"\"\"\n        Copy blocks between fbo's using Blit\n        \"\"\"\n        gl.glBindFramebuffer(gl.GL_READ_FRAMEBUFFER, src_fbo.glo)\n        gl.glBindFramebuffer(gl.GL_DRAW_FRAMEBUFFER, dst_fbo.glo)\n        gl.glBlitFramebuffer(\n            *src_fbo.viewport,\n            *dst_fbo.viewport,\n            gl.GL_COLOR_BUFFER_BIT, gl.GL_LINEAR\n        )\n\n    def get_raw_fbo_data(self, dtype: str = 'f1') -> bytes:\n        self.blit(self.fbo, self.draw_fbo)\n        return self.draw_fbo.read(\n            viewport=self.draw_fbo.viewport,\n            components=self.n_channels,\n            dtype=dtype,\n        )\n\n    def get_image(self) -> Image.Image:\n        return Image.frombytes(\n            'RGBA',\n            self.get_pixel_shape(),\n            self.get_raw_fbo_data(),\n            'raw', 'RGBA', 0, -1\n        )\n\n    def get_pixel_array(self) -> np.ndarray:\n        raw = self.get_raw_fbo_data(dtype='f4')\n        flat_arr = np.frombuffer(raw, dtype='f4')\n        arr = flat_arr.reshape([*reversed(self.draw_fbo.size), self.n_channels])\n        arr = arr[::-1]\n        # Convert from float\n        return (self.rgb_max_val * arr).astype(self.pixel_array_dtype)\n\n    # Needed?\n    def get_texture(self) -> moderngl.Texture:\n        texture = self.ctx.texture(\n            size=self.fbo.size,\n            components=4,\n            data=self.get_raw_fbo_data(),\n            dtype='f4'\n        )\n        return texture\n\n    # Getting camera attributes\n    def get_pixel_size(self) -> float:\n        return self.frame.get_width() / self.get_pixel_shape()[0]\n\n    def get_pixel_shape(self) -> tuple[int, int]:\n        return self.fbo.size\n\n    def get_pixel_width(self) -> int:\n        return self.get_pixel_shape()[0]\n\n    def get_pixel_height(self) -> int:\n        return self.get_pixel_shape()[1]\n\n    def get_aspect_ratio(self):\n        pw, ph = self.get_pixel_shape()\n        return pw / ph\n\n    def get_frame_height(self) -> float:\n        return self.frame.get_height()\n\n    def get_frame_width(self) -> float:\n        return self.frame.get_width()\n\n    def get_frame_shape(self) -> tuple[float, float]:\n        return (self.get_frame_width(), self.get_frame_height())\n\n    def get_frame_center(self) -> np.ndarray:\n        return self.frame.get_center()\n\n    def get_location(self) -> tuple[float, float, float]:\n        return self.frame.get_implied_camera_location()\n\n    def resize_frame_shape(self, fixed_dimension: bool = False) -> None:\n        \"\"\"\n        Changes frame_shape to match the aspect ratio\n        of the pixels, where fixed_dimension determines\n        whether frame_height or frame_width\n        remains fixed while the other changes accordingly.\n        \"\"\"\n        frame_height = self.get_frame_height()\n        frame_width = self.get_frame_width()\n        aspect_ratio = self.get_aspect_ratio()\n        if not fixed_dimension:\n            frame_height = frame_width / aspect_ratio\n        else:\n            frame_width = aspect_ratio * frame_height\n        self.frame.set_height(frame_height, stretch=True)\n        self.frame.set_width(frame_width, stretch=True)\n\n    # Rendering\n    def capture(self, *mobjects: Mobject) -> None:\n        self.clear()\n        self.refresh_uniforms()\n        self.fbo.use()\n        for mobject in mobjects:\n            mobject.render(self.ctx, self.uniforms)\n\n        if self.window:\n            self.window.swap_buffers()\n            if self.fbo is not self.window_fbo:\n                self.blit(self.fbo, self.window_fbo)\n                self.window.swap_buffers()\n\n    def refresh_uniforms(self) -> None:\n        frame = self.frame\n        view_matrix = frame.get_view_matrix()\n        light_pos = self.light_source.get_location()\n        cam_pos = self.frame.get_implied_camera_location()\n\n        self.uniforms.update(\n            view=tuple(view_matrix.T.flatten()),\n            frame_scale=frame.get_scale(),\n            frame_rescale_factors=(\n                2.0 / FRAME_WIDTH,\n                2.0 / FRAME_HEIGHT,\n                frame.get_scale() / frame.get_focal_distance(),\n            ),\n            pixel_size=self.get_pixel_size(),\n            camera_position=tuple(cam_pos),\n            light_position=tuple(light_pos),\n        )\n\n\n# Mostly just defined so old scenes don't break\nclass ThreeDCamera(Camera):\n    def __init__(self, samples: int = 4, **kwargs):\n        super().__init__(samples=samples, **kwargs)\n"
  },
  {
    "path": "manimlib/camera/camera_frame.py",
    "content": "from __future__ import annotations\n\nimport math\nimport warnings\n\nimport numpy as np\nfrom scipy.spatial.transform import Rotation\n\nfrom manimlib.constants import DEG, RADIANS\nfrom manimlib.constants import FRAME_SHAPE\nfrom manimlib.constants import DOWN, LEFT, ORIGIN, OUT, RIGHT, UP\nfrom manimlib.constants import PI\nfrom manimlib.mobject.mobject import Mobject\nfrom manimlib.utils.space_ops import normalize\nfrom manimlib.utils.simple_functions import clip\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from manimlib.typing import Vect3\n\n\nclass CameraFrame(Mobject):\n    def __init__(\n        self,\n        frame_shape: tuple[float, float] = FRAME_SHAPE,\n        center_point: Vect3 = ORIGIN,\n        # Field of view in the y direction\n        fovy: float = 45 * DEG,\n        euler_axes: str = \"zxz\",\n        # This keeps it ordered first in a scene\n        z_index=-1,\n        **kwargs,\n    ):\n        super().__init__(z_index=z_index, **kwargs)\n\n        self.uniforms[\"orientation\"] = Rotation.identity().as_quat()\n        self.uniforms[\"fovy\"] = fovy\n\n        self.default_orientation = Rotation.identity()\n        self.view_matrix = np.identity(4)\n        self.id4x4 = np.identity(4)\n        self.camera_location = OUT  # This will be updated by set_points\n        self.euler_axes = euler_axes\n\n        self.set_points(np.array([ORIGIN, LEFT, RIGHT, DOWN, UP]))\n        self.set_width(frame_shape[0], stretch=True)\n        self.set_height(frame_shape[1], stretch=True)\n        self.move_to(center_point)\n\n    def set_orientation(self, rotation: Rotation):\n        self.uniforms[\"orientation\"][:] = rotation.as_quat()\n        return self\n\n    def get_orientation(self):\n        return Rotation.from_quat(self.uniforms[\"orientation\"])\n\n    def make_orientation_default(self):\n        self.default_orientation = self.get_orientation()\n        return self\n\n    def to_default_state(self):\n        self.set_shape(*FRAME_SHAPE)\n        self.center()\n        self.set_orientation(self.default_orientation)\n        return self\n\n    def get_euler_angles(self) -> np.ndarray:\n        orientation = self.get_orientation()\n        if np.isclose(orientation.as_quat(), [0, 0, 0, 1]).all():\n            return np.zeros(3)\n        with warnings.catch_warnings():\n            warnings.simplefilter('ignore', UserWarning)  # Ignore UserWarnings\n            angles = orientation.as_euler(self.euler_axes)[::-1]\n        # Handle Gimble lock case\n        if self.euler_axes == \"zxz\":\n            if np.isclose(angles[1], 0, atol=1e-2):\n                angles[0] = angles[0] + angles[2]\n                angles[2] = 0\n            if np.isclose(angles[1], PI, atol=1e-2):\n                angles[0] = angles[0] - angles[2]\n                angles[2] = 0\n        return angles\n\n    def get_theta(self):\n        return self.get_euler_angles()[0]\n\n    def get_phi(self):\n        return self.get_euler_angles()[1]\n\n    def get_gamma(self):\n        return self.get_euler_angles()[2]\n\n    def get_scale(self):\n        return self.get_height() / FRAME_SHAPE[1]\n\n    def get_inverse_camera_rotation_matrix(self):\n        return self.get_orientation().as_matrix().T\n\n    def get_view_matrix(self, refresh=False):\n        \"\"\"\n        Returns a 4x4 for the affine transformation mapping a point\n        into the camera's internal coordinate system\n        \"\"\"\n        if self._data_has_changed:\n            shift = self.id4x4.copy()\n            rotation = self.id4x4.copy()\n\n            scale = self.get_scale()\n            shift[:3, 3] = -self.get_center()\n            rotation[:3, :3] = self.get_inverse_camera_rotation_matrix()\n            np.dot(rotation, shift, out=self.view_matrix)\n            if scale > 0:\n                self.view_matrix[:3, :4] /= scale\n\n        return self.view_matrix\n\n    def get_inv_view_matrix(self):\n        return np.linalg.inv(self.get_view_matrix())\n\n    @Mobject.affects_data\n    def interpolate(self, *args, **kwargs):\n        super().interpolate(*args, **kwargs)\n\n    @Mobject.affects_data\n    def rotate(self, angle: float, axis: np.ndarray = OUT, **kwargs):\n        rot = Rotation.from_rotvec(angle * normalize(axis))\n        self.set_orientation(rot * self.get_orientation())\n        return self\n\n    def set_euler_angles(\n        self,\n        theta: float | None = None,\n        phi: float | None = None,\n        gamma: float | None = None,\n        units: float = RADIANS\n    ):\n        eulers = self.get_euler_angles()  # theta, phi, gamma\n        for i, var in enumerate([theta, phi, gamma]):\n            if var is not None:\n                eulers[i] = var * units\n        if all(eulers == 0):\n            rot = Rotation.identity()\n        else:\n            rot = Rotation.from_euler(self.euler_axes, eulers[::-1])\n        self.set_orientation(rot)\n        return self\n\n    def increment_euler_angles(\n        self,\n        dtheta: float = 0,\n        dphi: float = 0,\n        dgamma: float = 0,\n        units: float = RADIANS\n    ):\n        angles = self.get_euler_angles()\n        new_angles = angles + np.array([dtheta, dphi, dgamma]) * units\n\n        # Limit range for phi\n        if self.euler_axes == \"zxz\":\n            new_angles[1] = clip(new_angles[1], 0, PI)\n        elif self.euler_axes == \"zxy\":\n            new_angles[1] = clip(new_angles[1], -PI / 2, PI / 2)\n\n        new_rot = Rotation.from_euler(self.euler_axes, new_angles[::-1])\n        self.set_orientation(new_rot)\n        return self\n\n    def set_euler_axes(self, seq: str):\n        self.euler_axes = seq\n\n    def reorient(\n        self,\n        theta_degrees: float | None = None,\n        phi_degrees: float | None = None,\n        gamma_degrees: float | None = None,\n        center: Vect3 | tuple[float, float, float] | None = None,\n        height: float | None = None\n    ):\n        \"\"\"\n        Shortcut for set_euler_angles, defaulting to taking\n        in angles in degrees\n        \"\"\"\n        self.set_euler_angles(theta_degrees, phi_degrees, gamma_degrees, units=DEG)\n        if center is not None:\n            self.move_to(np.array(center))\n        if height is not None:\n            self.set_height(height)\n        return self\n\n    def set_theta(self, theta: float):\n        return self.set_euler_angles(theta=theta)\n\n    def set_phi(self, phi: float):\n        return self.set_euler_angles(phi=phi)\n\n    def set_gamma(self, gamma: float):\n        return self.set_euler_angles(gamma=gamma)\n\n    def increment_theta(self, dtheta: float, units=RADIANS):\n        self.increment_euler_angles(dtheta=dtheta, units=units)\n        return self\n\n    def increment_phi(self, dphi: float, units=RADIANS):\n        self.increment_euler_angles(dphi=dphi, units=units)\n        return self\n\n    def increment_gamma(self, dgamma: float, units=RADIANS):\n        self.increment_euler_angles(dgamma=dgamma, units=units)\n        return self\n\n    def add_ambient_rotation(self, angular_speed=1 * DEG):\n        self.add_updater(lambda m, dt: m.increment_theta(angular_speed * dt))\n        return self\n\n    @Mobject.affects_data\n    def set_focal_distance(self, focal_distance: float):\n        self.uniforms[\"fovy\"] = 2 * math.atan(0.5 * self.get_height() / focal_distance)\n        return self\n\n    @Mobject.affects_data\n    def set_field_of_view(self, field_of_view: float):\n        self.uniforms[\"fovy\"] = field_of_view\n        return self\n\n    def get_shape(self):\n        return (self.get_width(), self.get_height())\n\n    def get_aspect_ratio(self):\n        width, height = self.get_shape()\n        return width / height\n\n    def get_center(self) -> np.ndarray:\n        # Assumes first point is at the center\n        return self.get_points()[0]\n\n    def get_width(self) -> float:\n        points = self.get_points()\n        return points[2, 0] - points[1, 0]\n\n    def get_height(self) -> float:\n        points = self.get_points()\n        return points[4, 1] - points[3, 1]\n\n    def get_focal_distance(self) -> float:\n        return 0.5 * self.get_height() / math.tan(0.5 * self.uniforms[\"fovy\"])\n\n    def get_field_of_view(self) -> float:\n        return self.uniforms[\"fovy\"]\n\n    def get_implied_camera_location(self) -> np.ndarray:\n        if self._data_has_changed:\n            to_camera = self.get_inverse_camera_rotation_matrix()[2]\n            dist = self.get_focal_distance()\n            self.camera_location = self.get_center() + dist * to_camera\n        return self.camera_location\n\n    def to_fixed_frame_point(self, point: Vect3, relative: bool = False):\n        view = self.get_view_matrix()\n        point4d = [*point, 0 if relative else 1]\n        return np.dot(point4d, view.T)[:3]\n\n    def from_fixed_frame_point(self, point: Vect3, relative: bool = False):\n        inv_view = self.get_inv_view_matrix()\n        point4d = [*point, 0 if relative else 1]\n        return np.dot(point4d, inv_view.T)[:3]\n"
  },
  {
    "path": "manimlib/config.py",
    "content": "from __future__ import annotations\n\nimport argparse\nimport colour\nimport importlib\nimport inspect\nimport os\nimport sys\nimport yaml\nfrom pathlib import Path\nfrom ast import literal_eval\nfrom addict import Dict\n\nfrom manimlib.logger import log\nfrom manimlib.utils.dict_ops import merge_dicts_recursively\n\nfrom typing import TYPE_CHECKING\nif TYPE_CHECKING:\n    from argparse import Namespace\n    from typing import Optional\n\n\ndef initialize_manim_config() -> Dict:\n    \"\"\"\n    Return default configuration for various classes in manim, such as\n    Scene, Window, Camera, and SceneFileWriter, as well as configuration\n    determining how the scene is run (e.g. written to file or previewed in window).\n\n    The result is initially on the contents of default_config.yml in the manimlib directory,\n    which can be further updated by a custom configuration file custom_config.yml.\n    It is further updated based on command line argument.\n    \"\"\"\n    args = parse_cli()\n    global_defaults_file = os.path.join(get_manim_dir(), \"manimlib\", \"default_config.yml\")\n    config = Dict(merge_dicts_recursively(\n        load_yaml(global_defaults_file),\n        load_yaml(\"custom_config.yml\"),  # From current working directory\n        load_yaml(args.config_file) if args.config_file else dict(),\n    ))\n\n    log.setLevel(args.log_level or config[\"log_level\"])\n\n    update_directory_config(config)\n    update_window_config(config, args)\n    update_camera_config(config, args)\n    update_file_writer_config(config, args)\n    update_scene_config(config, args)\n    update_run_config(config, args)\n    update_embed_config(config, args)\n\n    return config\n\n\ndef parse_cli():\n    try:\n        parser = argparse.ArgumentParser()\n        module_location = parser.add_mutually_exclusive_group()\n        module_location.add_argument(\n            \"file\",\n            nargs=\"?\",\n            help=\"Path to file holding the python code for the scene\",\n        )\n        parser.add_argument(\n            \"scene_names\",\n            nargs=\"*\",\n            help=\"Name of the Scene class you want to see\",\n        )\n        parser.add_argument(\n            \"-w\", \"--write_file\",\n            action=\"store_true\",\n            help=\"Render the scene as a movie file\",\n        )\n        parser.add_argument(\n            \"-s\", \"--skip_animations\",\n            action=\"store_true\",\n            help=\"Save the last frame\",\n        )\n        parser.add_argument(\n            \"-l\", \"--low_quality\",\n            action=\"store_true\",\n            help=\"Render at 480p\",\n        )\n        parser.add_argument(\n            \"-m\", \"--medium_quality\",\n            action=\"store_true\",\n            help=\"Render at 720p\",\n        )\n        parser.add_argument(\n            \"--hd\",\n            action=\"store_true\",\n            help=\"Render at a 1080p\",\n        )\n        parser.add_argument(\n            \"--uhd\",\n            action=\"store_true\",\n            help=\"Render at a 4k\",\n        )\n        parser.add_argument(\n            \"-f\", \"--full_screen\",\n            action=\"store_true\",\n            help=\"Show window in full screen\",\n        )\n        parser.add_argument(\n            \"-p\", \"--presenter_mode\",\n            action=\"store_true\",\n            help=\"Scene will stay paused during wait calls until \" + \\\n                 \"space bar or right arrow is hit, like a slide show\"\n        )\n        parser.add_argument(\n            \"-i\", \"--gif\",\n            action=\"store_true\",\n            help=\"Save the video as gif\",\n        )\n        parser.add_argument(\n            \"-t\", \"--transparent\",\n            action=\"store_true\",\n            help=\"Render to a movie file with an alpha channel\",\n        )\n        parser.add_argument(\n            \"--vcodec\",\n            help=\"Video codec to use with ffmpeg\",\n        )\n        parser.add_argument(\n            \"--pix_fmt\",\n            help=\"Pixel format to use for the output of ffmpeg, defaults to `yuv420p`\",\n        )\n        parser.add_argument(\n            \"-q\", \"--quiet\",\n            action=\"store_true\",\n            help=\"\",\n        )\n        parser.add_argument(\n            \"-a\", \"--write_all\",\n            action=\"store_true\",\n            help=\"Write all the scenes from a file\",\n        )\n        parser.add_argument(\n            \"-o\", \"--open\",\n            action=\"store_true\",\n            help=\"Automatically open the saved file once its done\",\n        )\n        parser.add_argument(\n            \"--finder\",\n            action=\"store_true\",\n            help=\"Show the output file in finder\",\n        )\n        parser.add_argument(\n            \"--subdivide\",\n            action=\"store_true\",\n            help=\"Divide the output animation into individual movie files \" +\n                 \"for each animation\",\n        )\n        parser.add_argument(\n            \"--file_name\",\n            help=\"Name for the movie or image file\",\n        )\n        parser.add_argument(\n            \"-n\", \"--start_at_animation_number\",\n            help=\"Start rendering not from the first animation, but \" + \\\n                 \"from another, specified by its index.  If you pass \" + \\\n                 \"in two comma separated values, e.g. \\\"3,6\\\", it will end \" + \\\n                 \"the rendering at the second value\",\n        )\n        parser.add_argument(\n            \"-e\", \"--embed\",\n            metavar=\"LINE_NUMBER\",\n            help=\"Adds a breakpoint at the inputted file dropping into an \" + \\\n                 \"interactive iPython session at that point of the code.\"\n        )\n        parser.add_argument(\n            \"-r\", \"--resolution\",\n            help=\"Resolution, passed as \\\"WxH\\\", e.g. \\\"1920x1080\\\"\",\n        )\n        parser.add_argument(\n            \"--fps\",\n            help=\"Frame rate, as an integer\",\n            type=int,\n        )\n        parser.add_argument(\n            \"-c\", \"--color\",\n            help=\"Background color\",\n        )\n        parser.add_argument(\n            \"--leave_progress_bars\",\n            action=\"store_true\",\n            help=\"Leave progress bars displayed in terminal\",\n        )\n        parser.add_argument(\n            \"--show_animation_progress\",\n            action=\"store_true\",\n            help=\"Show progress bar for each animation\",\n        )\n        parser.add_argument(\n            \"--prerun\",\n            action=\"store_true\",\n            help=\"Calculate total framecount, to display in a progress bar, by doing \" + \\\n                 \"an initial run of the scene which skips animations.\"\n        )\n        parser.add_argument(\n            \"--video_dir\",\n            help=\"Directory to write video\",\n        )\n        parser.add_argument(\n            \"--config_file\",\n            help=\"Path to the custom configuration file\",\n        )\n        parser.add_argument(\n            \"-v\", \"--version\",\n            action=\"store_true\",\n            help=\"Display the version of manimgl\"\n        )\n        parser.add_argument(\n            \"--log-level\",\n            help=\"Level of messages to Display, can be DEBUG / INFO / WARNING / ERROR / CRITICAL\"\n        )\n        parser.add_argument(\n            \"--clear-cache\",\n            action=\"store_true\",\n            help=\"Erase the cache used for Tex and Text Mobjects\"\n        )\n        parser.add_argument(\n            \"--autoreload\",\n            action=\"store_true\",\n            help=\"Automatically reload Python modules to pick up code changes \" +\n                 \"across different files\",\n        )\n        args = parser.parse_args()\n        args.write_file = any([args.write_file, args.open, args.finder])\n        return args\n    except argparse.ArgumentError as err:\n        log.error(str(err))\n        sys.exit(2)\n\n\ndef update_directory_config(config: Dict):\n    dir_config = config.directories\n    base = dir_config.base\n    for key, subdir in dir_config.subdirs.items():\n        dir_config[key] = os.path.join(base, subdir)\n\n\ndef update_window_config(config: Dict, args: Namespace):\n    window_config = config.window\n    for key in \"position\", \"size\":\n        if window_config.get(key):\n            window_config[key] = literal_eval(window_config[key])\n    if args.full_screen:\n        window_config.full_screen = True\n\n\ndef update_camera_config(config: Dict, args: Namespace):\n    camera_config = config.camera\n    arg_resolution = get_resolution_from_args(args, config.resolution_options)\n    camera_config.resolution = arg_resolution or literal_eval(camera_config.resolution)\n    if args.fps:\n        camera_config.fps = args.fps\n    if args.color:\n        try:\n            camera_config.background_color = colour.Color(args.color)\n        except Exception as err:\n            log.error(\"Please use a valid color\")\n            log.error(err)\n            sys.exit(2)\n    if args.transparent:\n        camera_config.background_opacity = 0.0\n\n\ndef update_file_writer_config(config: Dict, args: Namespace):\n    file_writer_config = config.file_writer\n    file_writer_config.update(\n        write_to_movie=(not args.skip_animations and args.write_file),\n        subdivide_output=args.subdivide,\n        save_last_frame=(args.skip_animations and args.write_file),\n        png_mode=(\"RGBA\" if args.transparent else \"RGB\"),\n        movie_file_extension=(get_file_ext(args)),\n        output_directory=get_output_directory(args, config),\n        file_name=args.file_name,\n        open_file_upon_completion=args.open,\n        show_file_location_upon_completion=args.finder,\n        quiet=args.quiet,\n    )\n\n    if args.vcodec:\n        file_writer_config.video_codec = args.vcodec\n    elif args.transparent:\n        file_writer_config.video_codec = 'prores_ks'\n        file_writer_config.pixel_format = ''\n    elif args.gif:\n        file_writer_config.video_codec = ''\n\n    if args.pix_fmt:\n        file_writer_config.pixel_format = args.pix_fmt\n\n\ndef update_scene_config(config: Dict, args: Namespace):\n    scene_config = config.scene\n    start, end = get_animations_numbers(args)\n    scene_config.update(\n        # Note, Scene.__init__ makes use of both manimlib.camera and\n        # manimlib.file_writer below, so the arguments here are just for\n        # any future specifications beyond what the global configuration holds\n        camera_config=dict(),\n        file_writer_config=dict(),\n        skip_animations=args.skip_animations,\n        start_at_animation_number=start,\n        end_at_animation_number=end,\n        presenter_mode=args.presenter_mode,\n    )\n    if args.leave_progress_bars:\n        scene_config.leave_progress_bars = True\n    if args.show_animation_progress:\n        scene_config.show_animation_progress = True\n\n\ndef update_run_config(config: Dict, args: Namespace):\n    config.run = Dict(\n        file_name=args.file,\n        embed_line=(int(args.embed) if args.embed is not None else None),\n        is_reload=False,\n        prerun=args.prerun,\n        scene_names=args.scene_names,\n        quiet=args.quiet or args.write_all,\n        write_all=args.write_all,\n        show_in_window=not args.write_file\n    )\n\n\ndef update_embed_config(config: Dict, args: Namespace):\n    if args.autoreload:\n        config.embed.autoreload = True\n\n\n# Helpers for the functions above\n\n\ndef load_yaml(file_path: str):\n    try:\n        with open(file_path, \"r\") as file:\n            return yaml.safe_load(file) or {}\n    except FileNotFoundError:\n        return {}\n\n\ndef get_manim_dir():\n    manimlib_module = importlib.import_module(\"manimlib\")\n    manimlib_dir = os.path.dirname(inspect.getabsfile(manimlib_module))\n    return os.path.abspath(os.path.join(manimlib_dir, \"..\"))\n\n\ndef get_resolution_from_args(args: Optional[Namespace], resolution_options: dict) -> Optional[tuple[int, int]]:\n    if args.resolution:\n        return tuple(map(int, args.resolution.split(\"x\")))\n    if args.low_quality:\n        return literal_eval(resolution_options[\"low\"])\n    if args.medium_quality:\n        return literal_eval(resolution_options[\"med\"])\n    if args.hd:\n        return literal_eval(resolution_options[\"high\"])\n    if args.uhd:\n        return literal_eval(resolution_options[\"4k\"])\n    return None\n\n\ndef get_file_ext(args: Namespace) -> str:\n    if args.transparent:\n        file_ext = \".mov\"\n    elif args.gif:\n        file_ext = \".gif\"\n    else:\n        file_ext = \".mp4\"\n    return file_ext\n\n\ndef get_animations_numbers(args: Namespace) -> tuple[int | None, int | None]:\n    stan = args.start_at_animation_number\n    if stan is None:\n        return (None, None)\n    elif \",\" in stan:\n        return tuple(map(int, stan.split(\",\")))\n    else:\n        return int(stan), None\n\n\ndef get_output_directory(args: Namespace, config: Dict) -> str:\n    dir_config = config.directories\n    out_dir = args.video_dir or dir_config.output\n    if dir_config.mirror_module_path and args.file:\n        file_path = Path(args.file).absolute()\n        if str(file_path).startswith(dir_config.removed_mirror_prefix):\n            rel_path = file_path.relative_to(dir_config.removed_mirror_prefix)\n            rel_path = Path(str(rel_path).lstrip(\"_\"))\n        else:\n            rel_path = file_path.stem\n        out_dir = Path(out_dir, rel_path).with_suffix(\"\")\n    return out_dir\n\n\n# Create global configuration\nmanim_config: Dict = initialize_manim_config()\n"
  },
  {
    "path": "manimlib/constants.py",
    "content": "from __future__ import annotations\nimport numpy as np\n\nfrom typing import TYPE_CHECKING\nif TYPE_CHECKING:\n    from typing import List\n    from manimlib.typing import ManimColor, Vect3\n\n# See manimlib/default_config.yml\nfrom manimlib.config import manim_config\n\n\nDEFAULT_RESOLUTION: tuple[int, int] = manim_config.camera.resolution\nDEFAULT_PIXEL_WIDTH: int = DEFAULT_RESOLUTION[0]\nDEFAULT_PIXEL_HEIGHT: int = DEFAULT_RESOLUTION[1]\n\n# Sizes relevant to default camera frame\nASPECT_RATIO: float = DEFAULT_PIXEL_WIDTH / DEFAULT_PIXEL_HEIGHT\nFRAME_HEIGHT: float = manim_config.sizes.frame_height\nFRAME_WIDTH: float = FRAME_HEIGHT * ASPECT_RATIO\nFRAME_SHAPE: tuple[float, float] = (FRAME_WIDTH, FRAME_HEIGHT)\nFRAME_Y_RADIUS: float = FRAME_HEIGHT / 2\nFRAME_X_RADIUS: float = FRAME_WIDTH / 2\n\n\n# Helpful values for positioning mobjects\nSMALL_BUFF: float = manim_config.sizes.small_buff\nMED_SMALL_BUFF: float = manim_config.sizes.med_small_buff\nMED_LARGE_BUFF: float = manim_config.sizes.med_large_buff\nLARGE_BUFF: float = manim_config.sizes.large_buff\n\nDEFAULT_MOBJECT_TO_EDGE_BUFF: float = manim_config.sizes.default_mobject_to_edge_buff\nDEFAULT_MOBJECT_TO_MOBJECT_BUFF: float = manim_config.sizes.default_mobject_to_mobject_buff\n\n\n# Standard vectors\nORIGIN: Vect3 = np.array([0., 0., 0.])\nUP: Vect3 = np.array([0., 1., 0.])\nDOWN: Vect3 = np.array([0., -1., 0.])\nRIGHT: Vect3 = np.array([1., 0., 0.])\nLEFT: Vect3 = np.array([-1., 0., 0.])\nIN: Vect3 = np.array([0., 0., -1.])\nOUT: Vect3 = np.array([0., 0., 1.])\nX_AXIS: Vect3 = np.array([1., 0., 0.])\nY_AXIS: Vect3 = np.array([0., 1., 0.])\nZ_AXIS: Vect3 = np.array([0., 0., 1.])\n\nNULL_POINTS = np.array([[0., 0., 0.]])\n\n# Useful abbreviations for diagonals\nUL: Vect3 = UP + LEFT\nUR: Vect3 = UP + RIGHT\nDL: Vect3 = DOWN + LEFT\nDR: Vect3 = DOWN + RIGHT\n\nTOP: Vect3 = FRAME_Y_RADIUS * UP\nBOTTOM: Vect3 = FRAME_Y_RADIUS * DOWN\nLEFT_SIDE: Vect3 = FRAME_X_RADIUS * LEFT\nRIGHT_SIDE: Vect3 = FRAME_X_RADIUS * RIGHT\n\n# Angles\nPI: float = np.pi\nTAU: float = 2 * PI\nDEG: float = TAU / 360\nDEGREES = DEG  # Many older animations use the full name\n# Nice to have a constant for readability\n# when juxtaposed with expressions like 30 * DEG\nRADIANS: float = 1\n\n# Related to Text\nNORMAL: str = \"NORMAL\"\nITALIC: str = \"ITALIC\"\nOBLIQUE: str = \"OBLIQUE\"\nBOLD: str = \"BOLD\"\n\nDEFAULT_STROKE_WIDTH: float = manim_config.vmobject.default_stroke_width\n\n# Colors\nBLUE_E: ManimColor = manim_config.colors.blue_e\nBLUE_D: ManimColor = manim_config.colors.blue_d\nBLUE_C: ManimColor = manim_config.colors.blue_c\nBLUE_B: ManimColor = manim_config.colors.blue_b\nBLUE_A: ManimColor = manim_config.colors.blue_a\nTEAL_E: ManimColor = manim_config.colors.teal_e\nTEAL_D: ManimColor = manim_config.colors.teal_d\nTEAL_C: ManimColor = manim_config.colors.teal_c\nTEAL_B: ManimColor = manim_config.colors.teal_b\nTEAL_A: ManimColor = manim_config.colors.teal_a\nGREEN_E: ManimColor = manim_config.colors.green_e\nGREEN_D: ManimColor = manim_config.colors.green_d\nGREEN_C: ManimColor = manim_config.colors.green_c\nGREEN_B: ManimColor = manim_config.colors.green_b\nGREEN_A: ManimColor = manim_config.colors.green_a\nYELLOW_E: ManimColor = manim_config.colors.yellow_e\nYELLOW_D: ManimColor = manim_config.colors.yellow_d\nYELLOW_C: ManimColor = manim_config.colors.yellow_c\nYELLOW_B: ManimColor = manim_config.colors.yellow_b\nYELLOW_A: ManimColor = manim_config.colors.yellow_a\nGOLD_E: ManimColor = manim_config.colors.gold_e\nGOLD_D: ManimColor = manim_config.colors.gold_d\nGOLD_C: ManimColor = manim_config.colors.gold_c\nGOLD_B: ManimColor = manim_config.colors.gold_b\nGOLD_A: ManimColor = manim_config.colors.gold_a\nRED_E: ManimColor = manim_config.colors.red_e\nRED_D: ManimColor = manim_config.colors.red_d\nRED_C: ManimColor = manim_config.colors.red_c\nRED_B: ManimColor = manim_config.colors.red_b\nRED_A: ManimColor = manim_config.colors.red_a\nMAROON_E: ManimColor = manim_config.colors.maroon_e\nMAROON_D: ManimColor = manim_config.colors.maroon_d\nMAROON_C: ManimColor = manim_config.colors.maroon_c\nMAROON_B: ManimColor = manim_config.colors.maroon_b\nMAROON_A: ManimColor = manim_config.colors.maroon_a\nPURPLE_E: ManimColor = manim_config.colors.purple_e\nPURPLE_D: ManimColor = manim_config.colors.purple_d\nPURPLE_C: ManimColor = manim_config.colors.purple_c\nPURPLE_B: ManimColor = manim_config.colors.purple_b\nPURPLE_A: ManimColor = manim_config.colors.purple_a\nGREY_E: ManimColor = manim_config.colors.grey_e\nGREY_D: ManimColor = manim_config.colors.grey_d\nGREY_C: ManimColor = manim_config.colors.grey_c\nGREY_B: ManimColor = manim_config.colors.grey_b\nGREY_A: ManimColor = manim_config.colors.grey_a\nWHITE: ManimColor = manim_config.colors.white\nBLACK: ManimColor = manim_config.colors.black\nGREY_BROWN: ManimColor = manim_config.colors.grey_brown\nDARK_BROWN: ManimColor = manim_config.colors.dark_brown\nLIGHT_BROWN: ManimColor = manim_config.colors.light_brown\nPINK: ManimColor = manim_config.colors.pink\nLIGHT_PINK: ManimColor = manim_config.colors.light_pink\nGREEN_SCREEN: ManimColor = manim_config.colors.green_screen\nORANGE: ManimColor = manim_config.colors.orange\nPURE_RED: ManimColor = manim_config.colors.pure_red\nPURE_GREEN: ManimColor = manim_config.colors.pure_green\nPURE_BLUE: ManimColor = manim_config.colors.pure_blue\n\nMANIM_COLORS: List[ManimColor] = list(manim_config.colors.values())\n\n# Abbreviated names for the \"median\" colors\nBLUE: ManimColor = BLUE_C\nTEAL: ManimColor = TEAL_C\nGREEN: ManimColor = GREEN_C\nYELLOW: ManimColor = YELLOW_C\nGOLD: ManimColor = GOLD_C\nRED: ManimColor = RED_C\nMAROON: ManimColor = MAROON_C\nPURPLE: ManimColor = PURPLE_C\nGREY: ManimColor = GREY_C\n\nCOLORMAP_3B1B: List[ManimColor] = [BLUE_E, GREEN, YELLOW, RED]\n\n# Default mobject colors should be configurable just like background color\n# DEFAULT_MOBJECT_COLOR is mainly for text, tex, line, etc... mobjects. Default is WHITE\n# DEFAULT_LIGHT_COLOR is mainly for things like axes, arrows, annulus and other lightly colored mobjects. Default is GREY_B\nDEFAULT_MOBJECT_COLOR: ManimColor = manim_config.mobject.default_mobject_color or WHITE\nDEFAULT_LIGHT_COLOR: ManimColor = manim_config.mobject.default_light_color or GREY_B\n\nDEFAULT_VMOBJECT_STROKE_COLOR : ManimColor = manim_config.vmobject.default_stroke_color or GREY_A\nDEFAULT_VMOBJECT_FILL_COLOR : ManimColor = manim_config.vmobject.default_fill_color or GREY_C\n"
  },
  {
    "path": "manimlib/default_config.yml",
    "content": "# This file determines the default configuration for how manim is\n# run, including names for directories it will write to, default\n# parameters for various classes, style choices, etc. To customize\n# your own, create a custom_config.yml file in whatever directory\n# you are running manim. For 3blue1brown, for instance, mind is\n# here: https://github.com/3b1b/videos/blob/master/custom_config.yml\n\n# Alternatively, you can create it wherever you like, and on running\n# manim, pass in `--config_file /path/to/custom/config/file.yml`\n\ndirectories:\n  # Set this to true if you want the path to video files\n  # to match the directory structure of the path to the\n  # source code generating that video\n  mirror_module_path: False\n  # Manim may write to and read from the file system, e.g.\n  # to render videos and to look for svg/png assets. This\n  # will specify where those assets live, with a base directory,\n  # and various subdirectory names within it\n  base: \"\"\n  subdirs:\n    # Where should manim output video and image files?\n    output: \"videos\"\n    # If you want to use images, manim will look to these folders to find them\n    raster_images: \"raster_images\"\n    vector_images: \"vector_images\"\n    three_d_models: \"three_d_models\"\n    # If you want to use sounds, manim will look here to find it.\n    sounds: \"sounds\"\n    # Place for other forms of data relevant to any projects, like csv's\n    data: \"data\"\n    # When downloading, say an image, where will it go?\n    downloads: \"downloads\"\n    # For storing cached LaTeX compilation results\n    latex_cache: \"latex_cache\"\n  # For certain object types, especially Tex and Text, manim will save information\n  # to file to prevent the need to re-compute, e.g. recompiling the latex. By default,\n  # it stores this saved data to whatever directory appdirs.user_cache_dir(\"manim\") returns,\n  # but here a user can specify a different cache location\n  cache: \"\"\nwindow:\n  # The position of window on screen. UR -> Upper Right, and likewise DL -> Down and Left,\n  # UO would be upper middle, etc.\n  position_string: UR\n  # If using multiple monitors, which one should show the window\n  monitor_index: 0\n  # If not full screen, the default to give it half the screen width\n  full_screen: False\n  # Other optional specifications that override the above include:\n  # position: (500, 500)  # Specific position, in pixel coordinates, for upper right corner\n  # size: (1920, 1080)  # Specific size, in pixels\ncamera:\n  resolution: (1920, 1080)\n  background_color: \"#333333\"\n  fps: 30\n  background_opacity: 1.0\nfile_writer:\n  # What command to use for ffmpeg\n  ffmpeg_bin: \"ffmpeg\"\n  # Parameters to pass into ffmpeg\n  video_codec: \"libx264\"\n  pixel_format: \"yuv420p\"\n  saturation: 1.0\n  gamma: 1.0\n# Most of the scene configuration will come from CLI arguments,\n# but defaults can be set here\nscene:\n  show_animation_progress: False\n  leave_progress_bars: False\n  # When skipping animations, should a single frame be rendered\n  # at the end of each play call?\n  preview_while_skipping: True\n  # How long does a scene pause on Scene.wait calls\n  default_wait_time: 1.0\nvmobject:\n  default_stroke_width: 4.0\n  default_stroke_color: \"#DDDDDD\"     # Default is GREY_A\n  default_fill_color: \"#888888\"       # Default is GREY_C\nmobject:\n  default_mobject_color: \"#FFFFFF\"    # Default is WHITE\n  default_light_color: \"#BBBBBB\"      # Default is GREY_B\ntex:\n  # See tex_templates.yml\n  template: \"default\"\n  # The font size at which Tex(\"0\") has a height of 1 manim unit\n  font_size_for_unit_height: 144\ntext:\n  # font: \"Cambria Math\"\n  font: \"Consolas\"\n  alignment: \"LEFT\"\n  # The font size at which Text(\"0\") has a height of 1 manim unit\n  font_size_for_unit_height: 144\nembed:\n  exception_mode: \"Verbose\"\n  autoreload: False\nresolution_options:\n  # When the user passes in -l, -m, --hd or --uhd, these are the corresponding\n  # resolutions\n  low: (854, 480)\n  med: (1280, 720)\n  high: (1920, 1080)\n  4k: (3840, 2160)\nsizes:\n  # This determines the scale of the manim coordinate system with respect to\n  # the viewing frame\n  frame_height: 8.0\n  # These determine the constants SMALL_BUFF, MED_SMALL_BUFF, etc., useful\n  # for nudging things around and having default spacing values\n  small_buff: 0.1\n  med_small_buff: 0.25\n  med_large_buff: 0.5\n  large_buff: 1.0\n  # Default buffers used in Mobject.next_to or Mobject.to_edge\n  default_mobject_to_edge_buff: 0.5\n  default_mobject_to_mobject_buff: 0.25\nkey_bindings:\n  pan_3d: \"d\"\n  pan: \"f\"\n  reset: \"r\"\n  quit: \"q\" # Together with command\n  select: \"s\"\n  unselect: \"u\"\n  grab: \"g\"\n  x_grab: \"h\"\n  y_grab: \"v\"\n  z_grab: \"z\"\n  resize: \"t\"\n  color: \"c\"\n  information: \"i\"\n  cursor: \"k\"\ncolors:\n  blue_e: \"#1C758A\"\n  blue_d: \"#29ABCA\"\n  blue_c: \"#58C4DD\"\n  blue_b: \"#9CDCEB\"\n  blue_a: \"#C7E9F1\"\n  teal_e: \"#49A88F\"\n  teal_d: \"#55C1A7\"\n  teal_c: \"#5CD0B3\"\n  teal_b: \"#76DDC0\"\n  teal_a: \"#ACEAD7\"\n  green_e: \"#699C52\"\n  green_d: \"#77B05D\"\n  green_c: \"#83C167\"\n  green_b: \"#A6CF8C\"\n  green_a: \"#C9E2AE\"\n  yellow_e: \"#E8C11C\"\n  yellow_d: \"#F4D345\"\n  yellow_c: \"#FFFF00\"\n  yellow_b: \"#FFEA94\"\n  yellow_a: \"#FFF1B6\"\n  gold_e: \"#C78D46\"\n  gold_d: \"#E1A158\"\n  gold_c: \"#F0AC5F\"\n  gold_b: \"#F9B775\"\n  gold_a: \"#F7C797\"\n  red_e: \"#CF5044\"\n  red_d: \"#E65A4C\"\n  red_c: \"#FC6255\"\n  red_b: \"#FF8080\"\n  red_a: \"#F7A1A3\"\n  maroon_e: \"#94424F\"\n  maroon_d: \"#A24D61\"\n  maroon_c: \"#C55F73\"\n  maroon_b: \"#EC92AB\"\n  maroon_a: \"#ECABC1\"\n  purple_e: \"#644172\"\n  purple_d: \"#715582\"\n  purple_c: \"#9A72AC\"\n  purple_b: \"#B189C6\"\n  purple_a: \"#CAA3E8\"\n  grey_e: \"#222222\"\n  grey_d: \"#444444\"\n  grey_c: \"#888888\"\n  grey_b: \"#BBBBBB\"\n  grey_a: \"#DDDDDD\"\n  white: \"#FFFFFF\"\n  black: \"#000000\"\n  grey_brown: \"#736357\"\n  dark_brown: \"#8B4513\"\n  light_brown: \"#CD853F\"\n  pink: \"#D147BD\"\n  light_pink: \"#DC75CD\"\n  green_screen: \"#00FF00\"\n  orange: \"#FF862F\"\n  pure_red: \"#FF0000\"\n  pure_green: \"#00FF00\"\n  pure_blue: \"#0000FF\"\n# Can be DEBUG / INFO / WARNING / ERROR / CRITICAL\nlog_level: \"INFO\"\nuniversal_import_line: \"from manimlib import *\"\nignore_manimlib_modules_on_reload: True\n"
  },
  {
    "path": "manimlib/event_handler/__init__.py",
    "content": "from manimlib.event_handler.event_dispatcher import EventDispatcher\n\n\n# This is supposed to be a Singleton\n# i.e., during runtime there should be only one object of Event Dispatcher\nEVENT_DISPATCHER = EventDispatcher()\n"
  },
  {
    "path": "manimlib/event_handler/event_dispatcher.py",
    "content": "from __future__ import annotations\n\nimport numpy as np\n\nfrom manimlib.event_handler.event_listner import EventListener\nfrom manimlib.event_handler.event_type import EventType\n\n\nclass EventDispatcher(object):\n    def __init__(self):\n        self.event_listners: dict[\n            EventType, list[EventListener]\n        ] = {\n            event_type: []\n            for event_type in EventType\n        }\n        self.mouse_point = np.array((0., 0., 0.))\n        self.mouse_drag_point = np.array((0., 0., 0.))\n        self.pressed_keys: set[int] = set()\n        self.draggable_object_listners: list[EventListener] = []\n\n    def add_listner(self, event_listner: EventListener):\n        assert isinstance(event_listner, EventListener)\n        self.event_listners[event_listner.event_type].append(event_listner)\n        return self\n\n    def remove_listner(self, event_listner: EventListener):\n        assert isinstance(event_listner, EventListener)\n        try:\n            while event_listner in self.event_listners[event_listner.event_type]:\n                self.event_listners[event_listner.event_type].remove(event_listner)\n        except:\n            # raise ValueError(\"Handler is not handling this event, so cannot remove it.\")\n            pass\n        return self\n\n    def dispatch(self, event_type: EventType, **event_data):\n        if event_type == EventType.MouseMotionEvent:\n            self.mouse_point = event_data[\"point\"]\n        elif event_type == EventType.MouseDragEvent:\n            self.mouse_drag_point = event_data[\"point\"]\n        elif event_type == EventType.KeyPressEvent:\n            self.pressed_keys.add(event_data[\"symbol\"])  # Modifiers?\n        elif event_type == EventType.KeyReleaseEvent:\n            self.pressed_keys.difference_update({event_data[\"symbol\"]})  # Modifiers?\n        elif event_type == EventType.MousePressEvent:\n            self.draggable_object_listners = [\n                listner\n                for listner in self.event_listners[EventType.MouseDragEvent]\n                if listner.mobject.is_point_touching(self.mouse_point)\n            ]\n        elif event_type == EventType.MouseReleaseEvent:\n            self.draggable_object_listners = []\n\n        propagate_event = None\n\n        if event_type == EventType.MouseDragEvent:\n            for listner in self.draggable_object_listners:\n                assert isinstance(listner, EventListener)\n                propagate_event = listner.callback(listner.mobject, event_data)\n                if propagate_event is not None and propagate_event is False:\n                    return propagate_event\n\n        elif event_type.value.startswith('mouse'):\n            for listner in self.event_listners[event_type]:\n                if listner.mobject.is_point_touching(self.mouse_point):\n                    propagate_event = listner.callback(\n                        listner.mobject, event_data)\n                    if propagate_event is not None and propagate_event is False:\n                        return propagate_event\n\n        elif event_type.value.startswith('key'):\n            for listner in self.event_listners[event_type]:\n                propagate_event = listner.callback(listner.mobject, event_data)\n                if propagate_event is not None and propagate_event is False:\n                    return propagate_event\n\n        return propagate_event\n\n    def get_listners_count(self) -> int:\n        return sum([len(value) for key, value in self.event_listners.items()])\n\n    def get_mouse_point(self) -> np.ndarray:\n        return self.mouse_point\n\n    def get_mouse_drag_point(self) -> np.ndarray:\n        return self.mouse_drag_point\n\n    def is_key_pressed(self, symbol: int) -> bool:\n        return (symbol in self.pressed_keys)\n\n    __iadd__ = add_listner\n    __isub__ = remove_listner\n    __call__ = dispatch\n    __len__ = get_listners_count\n"
  },
  {
    "path": "manimlib/event_handler/event_listner.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable\n\n    from manimlib.event_handler.event_type import EventType\n    from manimlib.mobject.mobject import Mobject\n\n\nclass EventListener(object):\n    def __init__(\n        self,\n        mobject: Mobject,\n        event_type: EventType,\n        event_callback: Callable[[Mobject, dict[str]]]\n    ):\n        self.mobject = mobject\n        self.event_type = event_type\n        self.callback = event_callback\n\n    def __eq__(self, o: object) -> bool:\n        return_val = False\n        try:\n            return_val = self.callback == o.callback \\\n                and self.mobject == o.mobject \\\n                and self.event_type == o.event_type\n        except:\n            pass\n        return return_val\n"
  },
  {
    "path": "manimlib/event_handler/event_type.py",
    "content": "from enum import Enum\n\n\nclass EventType(Enum):\n    MouseMotionEvent = 'mouse_motion_event'\n    MousePressEvent = 'mouse_press_event'\n    MouseReleaseEvent = 'mouse_release_event'\n    MouseDragEvent = 'mouse_drag_event'\n    MouseScrollEvent = 'mouse_scroll_event'\n    KeyPressEvent = 'key_press_event'\n    KeyReleaseEvent = 'key_release_event'\n"
  },
  {
    "path": "manimlib/extract_scene.py",
    "content": "from __future__ import annotations\n\nimport copy\nimport inspect\nimport sys\n\nfrom manimlib.module_loader import ModuleLoader\n\nfrom manimlib.config import manim_config\nfrom manimlib.logger import log\nfrom manimlib.scene.interactive_scene import InteractiveScene\nfrom manimlib.scene.scene import Scene\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    Module = importlib.util.types.ModuleType\n    from typing import Optional\n    from addict import Dict\n\n\nclass BlankScene(InteractiveScene):\n    def construct(self):\n        exec(manim_config.universal_import_line)\n        self.embed()\n\n\ndef is_child_scene(obj, module):\n    if not inspect.isclass(obj):\n        return False\n    if not issubclass(obj, Scene):\n        return False\n    if obj == Scene:\n        return False\n    if not obj.__module__.startswith(module.__name__):\n        return False\n    return True\n\n\ndef prompt_user_for_choice(scene_classes):\n    name_to_class = {}\n    max_digits = len(str(len(scene_classes)))\n    for idx, scene_class in enumerate(scene_classes, start=1):\n        name = scene_class.__name__\n        print(f\"{str(idx).zfill(max_digits)}: {name}\")\n        name_to_class[name] = scene_class\n    try:\n        user_input = input(\"\\nSelect which scene to render (by name or number): \")\n        return [\n            name_to_class[split_str] if not split_str.isnumeric() else scene_classes[int(split_str) - 1]\n            for split_str in user_input.replace(\" \", \"\").split(\",\")\n        ]\n    except IndexError:\n        log.error(\"Invalid scene number\")\n        sys.exit(2)\n    except KeyError:\n        log.error(\"Invalid scene name\")\n        sys.exit(2)\n    except EOFError:\n        sys.exit(1)\n\n\ndef compute_total_frames(scene_class, scene_config):\n    \"\"\"\n    When a scene is being written to file, a copy of the scene is run with\n    skip_animations set to true so as to count how many frames it will require.\n    This allows for a total progress bar on rendering, and also allows runtime\n    errors to be exposed preemptively for long running scenes.\n    \"\"\"\n    pre_config = copy.deepcopy(scene_config)\n    pre_config[\"file_writer_config\"][\"write_to_movie\"] = False\n    pre_config[\"file_writer_config\"][\"save_last_frame\"] = False\n    pre_config[\"file_writer_config\"][\"quiet\"] = True\n    pre_config[\"skip_animations\"] = True\n    pre_scene = scene_class(**pre_config)\n    pre_scene.run()\n    total_time = pre_scene.time - pre_scene.skip_time\n    return int(total_time * manim_config.camera.fps)\n\n\ndef scene_from_class(scene_class, scene_config: Dict, run_config: Dict):\n    fw_config = manim_config.file_writer\n    if fw_config.write_to_movie and run_config.prerun:\n        scene_config.file_writer_config.total_frames = compute_total_frames(scene_class, scene_config)\n    return scene_class(**scene_config)\n\n\ndef note_missing_scenes(arg_names, module_names):\n    for name in arg_names:\n        if name not in module_names:\n            log.error(f\"No scene named {name} found\")\n\n\ndef get_scenes_to_render(all_scene_classes: list, scene_config: Dict, run_config: Dict):\n    if run_config[\"write_all\"] or len(all_scene_classes) == 1:\n        classes_to_run = all_scene_classes\n    else:\n        name_to_class = {sc.__name__: sc for sc in all_scene_classes}\n        classes_to_run = [name_to_class.get(name) for name in run_config.scene_names]\n        classes_to_run = list(filter(lambda x: x, classes_to_run))  # Remove Nones\n        note_missing_scenes(run_config.scene_names, name_to_class.keys())\n\n    if len(classes_to_run) == 0:\n        classes_to_run = prompt_user_for_choice(all_scene_classes)\n\n    return [\n        scene_from_class(scene_class, scene_config, run_config)\n        for scene_class in classes_to_run\n    ]\n\n\ndef get_scene_classes(module: Optional[Module]):\n    if module is None:\n        # If no module was passed in, just play the blank scene\n        return [BlankScene]\n    if hasattr(module, \"SCENES_IN_ORDER\"):\n        return module.SCENES_IN_ORDER\n    else:\n        return [\n            member[1]\n            for member in inspect.getmembers(\n                module,\n                lambda x: is_child_scene(x, module)\n            )\n        ]\n\n\ndef get_indent(code_lines: list[str], line_number: int) -> str:\n    \"\"\"\n    Find the indent associated with a given line of python code,\n    as a string of spaces\n    \"\"\"\n    # Find most recent non-empty line\n    try:\n        line = next(filter(lambda line: line.strip(), code_lines[line_number - 1::-1]))\n    except StopIteration:\n        return \"\"\n\n    # Either return its leading spaces, or add for if it ends with colon\n    n_spaces = len(line) - len(line.lstrip())\n    if line.endswith(\":\"):\n        n_spaces += 4\n    return n_spaces * \" \"\n\n\ndef insert_embed_line_to_module(module: Module, run_config: Dict) -> None:\n    \"\"\"\n    This is hacky, but convenient. When user includes the argument \"-e\", it will try\n    to recreate a file that inserts the line `self.embed()` into the end of the scene's\n    construct method. If there is an argument passed in, it will insert the line after\n    the last line in the sourcefile which includes that string.\n    \"\"\"\n    lines = inspect.getsource(module).splitlines()\n    line_number = run_config.embed_line\n\n    # Add the relevant embed line to the code\n    indent = get_indent(lines, line_number)\n    lines.insert(line_number, indent + \"self.embed()\")\n    new_code = \"\\n\".join(lines)\n\n    # When the user executes the `-e <line_number>` command\n    # without specifying scene_names, the nearest class name above\n    # `<line_number>` will be automatically used as 'scene_names'.\n\n    if not run_config.scene_names:\n        classes = list(filter(lambda line: line.startswith(\"class\"), lines[:line_number]))\n        if classes:\n            from re import search\n\n            scene_name = search(r\"(\\w+)\\(\", classes[-1])\n            run_config.update(scene_names=[scene_name.group(1)])\n        else:\n            log.error(f\"No 'class' found above {line_number}!\")\n\n    # Execute the code, which presumably redefines the user's\n    # scene to include this embed line, within the relevant module.\n    code_object = compile(new_code, module.__name__, 'exec')\n    exec(code_object, module.__dict__)\n\n\ndef get_module(run_config: Dict) -> Module:\n    module = ModuleLoader.get_module(run_config.file_name, run_config.is_reload)\n    if run_config.embed_line:\n        insert_embed_line_to_module(module, run_config)\n    return module\n\n\ndef main(scene_config: Dict, run_config: Dict):\n    module = get_module(run_config)\n    all_scene_classes = get_scene_classes(module)\n    scenes = get_scenes_to_render(all_scene_classes, scene_config, run_config)\n    if len(scenes) == 0:\n        print(\"No scenes found to run\")\n    return scenes\n"
  },
  {
    "path": "manimlib/logger.py",
    "content": "import logging\n\nfrom rich.logging import RichHandler\n\n__all__ = [\"log\"]\n\n\nFORMAT = \"%(message)s\"\nlogging.basicConfig(\n    level=logging.WARNING, format=FORMAT, datefmt=\"[%X]\", handlers=[RichHandler()]\n)\n\nlog = logging.getLogger(\"manimgl\")\n"
  },
  {
    "path": "manimlib/mobject/__init__.py",
    "content": ""
  },
  {
    "path": "manimlib/mobject/boolean_ops.py",
    "content": "from __future__ import annotations\r\n\r\nimport numpy as np\r\nimport pathops\r\n\r\nfrom manimlib.mobject.types.vectorized_mobject import VMobject\r\n\r\n\r\n# Boolean operations between 2D mobjects\r\n# Borrowed from https://github.com/ManimCommunity/manim/\r\n\r\ndef _convert_vmobject_to_skia_path(vmobject: VMobject) -> pathops.Path:\r\n    path = pathops.Path()\r\n    for submob in vmobject.family_members_with_points():\r\n        for subpath in submob.get_subpaths():\r\n            quads = vmobject.get_bezier_tuples_from_points(subpath)\r\n            start = subpath[0]\r\n            path.moveTo(*start[:2])\r\n            for p0, p1, p2 in quads:\r\n                path.quadTo(*p1[:2], *p2[:2])\r\n            if vmobject.consider_points_equal(subpath[0], subpath[-1]):\r\n                path.close()\r\n    return path\r\n\r\n\r\ndef _convert_skia_path_to_vmobject(\r\n    path: pathops.Path,\r\n    vmobject: VMobject\r\n) -> VMobject:\r\n    PathVerb = pathops.PathVerb\r\n    current_path_start = np.array([0.0, 0.0, 0.0])\r\n    for path_verb, points in path:\r\n        if path_verb == PathVerb.CLOSE:\r\n            vmobject.add_line_to(current_path_start)\r\n        else:\r\n            points = np.hstack((np.array(points), np.zeros((len(points), 1))))\r\n            if path_verb == PathVerb.MOVE:\r\n                for point in points:\r\n                    current_path_start = point\r\n                    vmobject.start_new_path(point)\r\n            elif path_verb == PathVerb.CUBIC:\r\n                vmobject.add_cubic_bezier_curve_to(*points)\r\n            elif path_verb == PathVerb.LINE:\r\n                vmobject.add_line_to(points[0])\r\n            elif path_verb == PathVerb.QUAD:\r\n                vmobject.add_quadratic_bezier_curve_to(*points)\r\n            else:\r\n                raise Exception(f\"Unsupported: {path_verb}\")\r\n    return vmobject.reverse_points()\r\n\r\n\r\nclass Union(VMobject):\r\n    def __init__(self, *vmobjects: VMobject, **kwargs):\r\n        if len(vmobjects) < 2:\r\n            raise ValueError(\"At least 2 mobjects needed for Union.\")\r\n        super().__init__(**kwargs)\r\n        outpen = pathops.Path()\r\n        paths = [\r\n            _convert_vmobject_to_skia_path(vmobject)\r\n            for vmobject in vmobjects\r\n        ]\r\n        pathops.union(paths, outpen.getPen())\r\n        _convert_skia_path_to_vmobject(outpen, self)\r\n\r\n\r\nclass Difference(VMobject):\r\n    def __init__(self, subject: VMobject, clip: VMobject, **kwargs):\r\n        super().__init__(**kwargs)\r\n        outpen = pathops.Path()\r\n        pathops.difference(\r\n            [_convert_vmobject_to_skia_path(subject)],\r\n            [_convert_vmobject_to_skia_path(clip)],\r\n            outpen.getPen(),\r\n        )\r\n        _convert_skia_path_to_vmobject(outpen, self)\r\n\r\n\r\nclass Intersection(VMobject):\r\n    def __init__(self, *vmobjects: VMobject, **kwargs):\r\n        if len(vmobjects) < 2:\r\n            raise ValueError(\"At least 2 mobjects needed for Intersection.\")\r\n        super().__init__(**kwargs)\r\n        outpen = pathops.Path()\r\n        pathops.intersection(\r\n            [_convert_vmobject_to_skia_path(vmobjects[0])],\r\n            [_convert_vmobject_to_skia_path(vmobjects[1])],\r\n            outpen.getPen(),\r\n        )\r\n        new_outpen = outpen\r\n        for _i in range(2, len(vmobjects)):\r\n            new_outpen = pathops.Path()\r\n            pathops.intersection(\r\n                [outpen],\r\n                [_convert_vmobject_to_skia_path(vmobjects[_i])],\r\n                new_outpen.getPen(),\r\n            )\r\n            outpen = new_outpen\r\n        _convert_skia_path_to_vmobject(outpen, self)\r\n\r\n\r\nclass Exclusion(VMobject):\r\n    def __init__(self, *vmobjects: VMobject, **kwargs):\r\n        if len(vmobjects) < 2:\r\n            raise ValueError(\"At least 2 mobjects needed for Exclusion.\")\r\n        super().__init__(**kwargs)\r\n        outpen = pathops.Path()\r\n        pathops.xor(\r\n            [_convert_vmobject_to_skia_path(vmobjects[0])],\r\n            [_convert_vmobject_to_skia_path(vmobjects[1])],\r\n            outpen.getPen(),\r\n        )\r\n        new_outpen = outpen\r\n        for _i in range(2, len(vmobjects)):\r\n            new_outpen = pathops.Path()\r\n            pathops.xor(\r\n                [outpen],\r\n                [_convert_vmobject_to_skia_path(vmobjects[_i])],\r\n                new_outpen.getPen(),\r\n            )\r\n            outpen = new_outpen\r\n        _convert_skia_path_to_vmobject(outpen, self)\r\n"
  },
  {
    "path": "manimlib/mobject/changing.py",
    "content": "from __future__ import annotations\n\nimport numpy as np\n\nfrom manimlib.constants import BLUE_B, BLUE_D, BLUE_E, GREY_BROWN, DEFAULT_MOBJECT_COLOR\nfrom manimlib.mobject.mobject import Mobject\nfrom manimlib.mobject.types.vectorized_mobject import VGroup\nfrom manimlib.mobject.types.vectorized_mobject import VMobject\nfrom manimlib.utils.rate_functions import smooth\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable, List, Iterable\n    from manimlib.typing import ManimColor, Vect3, Self\n\n\nclass AnimatedBoundary(VGroup):\n    def __init__(\n        self,\n        vmobject: VMobject,\n        colors: List[ManimColor] = [BLUE_D, BLUE_B, BLUE_E, GREY_BROWN],\n        max_stroke_width: float = 3.0,\n        cycle_rate: float = 0.5,\n        back_and_forth: bool = True,\n        draw_rate_func: Callable[[float], float] = smooth,\n        fade_rate_func: Callable[[float], float] = smooth,\n        **kwargs\n    ):\n        super().__init__(**kwargs)\n        self.vmobject: VMobject = vmobject\n        self.colors = colors\n        self.max_stroke_width = max_stroke_width\n        self.cycle_rate = cycle_rate\n        self.back_and_forth = back_and_forth\n        self.draw_rate_func = draw_rate_func\n        self.fade_rate_func = fade_rate_func\n\n        self.boundary_copies: list[VMobject] = [\n            vmobject.copy().set_style(\n                stroke_width=0,\n                fill_opacity=0\n            )\n            for x in range(2)\n        ]\n        self.add(*self.boundary_copies)\n        self.total_time: float = 0\n        self.add_updater(\n            lambda m, dt: self.update_boundary_copies(dt)\n        )\n\n    def update_boundary_copies(self, dt: float) -> Self:\n        # Not actual time, but something which passes at\n        # an altered rate to make the implementation below\n        # cleaner\n        time = self.total_time * self.cycle_rate\n        growing, fading = self.boundary_copies\n        colors = self.colors\n        msw = self.max_stroke_width\n        vmobject = self.vmobject\n\n        index = int(time % len(colors))\n        alpha = time % 1\n        draw_alpha = self.draw_rate_func(alpha)\n        fade_alpha = self.fade_rate_func(alpha)\n\n        if self.back_and_forth and int(time) % 2 == 1:\n            bounds = (1 - draw_alpha, 1)\n        else:\n            bounds = (0, draw_alpha)\n        self.full_family_become_partial(growing, vmobject, *bounds)\n        growing.set_stroke(colors[index], width=msw)\n\n        if time >= 1:\n            self.full_family_become_partial(fading, vmobject, 0, 1)\n            fading.set_stroke(\n                color=colors[index - 1],\n                width=(1 - fade_alpha) * msw\n            )\n\n        self.total_time += dt\n        return self\n\n    def full_family_become_partial(\n        self,\n        mob1: VMobject,\n        mob2: VMobject,\n        a: float,\n        b: float\n    ) -> Self:\n        family1 = mob1.family_members_with_points()\n        family2 = mob2.family_members_with_points()\n        for sm1, sm2 in zip(family1, family2):\n            sm1.pointwise_become_partial(sm2, a, b)\n        return self\n\n\nclass TracedPath(VMobject):\n    def __init__(\n        self,\n        traced_point_func: Callable[[], Vect3],\n        time_traced: float = np.inf,\n        time_per_anchor: float = 1.0 / 15,\n        stroke_color: ManimColor = DEFAULT_MOBJECT_COLOR,\n        stroke_width: float | Iterable[float] = 2.0,\n        stroke_opacity: float = 1.0,\n        **kwargs\n    ):\n        self.stroke_config = dict(\n            color=stroke_color,\n            width=stroke_width,\n            opacity=stroke_opacity,\n        )\n\n        super().__init__(**kwargs)\n        self.traced_point_func = traced_point_func\n        self.time_traced = time_traced\n        self.time_per_anchor = time_per_anchor\n        self.time: float = 0\n        self.traced_points: list[np.ndarray] = []\n        self.add_updater(lambda m, dt: m.update_path(dt))\n\n    def update_path(self, dt: float) -> Self:\n        if dt == 0:\n            return self\n        point = self.traced_point_func().copy()\n        self.traced_points.append(point)\n\n        if self.time_traced < np.inf:\n            n_relevant_points = int(self.time_traced / dt + 0.5)\n            n_tps = len(self.traced_points)\n            if n_tps < n_relevant_points:\n                points = self.traced_points + [point] * (n_relevant_points - n_tps)\n            else:\n                points = self.traced_points[n_tps - n_relevant_points:]\n            # Every now and then refresh the list\n            if n_tps > 10 * n_relevant_points:\n                self.traced_points = self.traced_points[-n_relevant_points:]\n        else:\n            points = self.traced_points\n\n        if points:\n            self.set_points_smoothly(points)\n\n        self.set_stroke(**self.stroke_config)\n\n        self.time += dt\n        return self\n\n\nclass TracingTail(TracedPath):\n    def __init__(\n        self,\n        mobject_or_func: Mobject | Callable[[], np.ndarray],\n        time_traced: float = 1.0,\n        stroke_color: ManimColor = DEFAULT_MOBJECT_COLOR,\n        stroke_width: float | Iterable[float] = (0, 3),\n        stroke_opacity: float | Iterable[float] = (0, 1),\n        **kwargs\n    ):\n        if isinstance(mobject_or_func, Mobject):\n            func = mobject_or_func.get_center\n        else:\n            func = mobject_or_func\n\n        super().__init__(\n            func,\n            time_traced=time_traced,\n            stroke_color=stroke_color,\n            stroke_width=stroke_width,\n            stroke_opacity=stroke_opacity,\n            **kwargs\n        )\n        curr_point = self.traced_point_func()\n        n_points = int(self.time_traced / self.time_per_anchor)\n        self.traced_points: list[np.ndarray] = n_points * [curr_point]\n"
  },
  {
    "path": "manimlib/mobject/coordinate_systems.py",
    "content": "from __future__ import annotations\n\nfrom abc import ABC, abstractmethod\nimport numbers\n\nimport numpy as np\nimport itertools as it\n\nfrom manimlib.constants import BLACK, BLUE, BLUE_D, BLUE_E, GREEN, GREY_A, RED, DEFAULT_MOBJECT_COLOR\nfrom manimlib.constants import DEG, PI\nfrom manimlib.constants import DL, UL, DOWN, DR, LEFT, ORIGIN, OUT, RIGHT, UP\nfrom manimlib.constants import FRAME_X_RADIUS, FRAME_Y_RADIUS\nfrom manimlib.constants import MED_SMALL_BUFF, SMALL_BUFF\nfrom manimlib.mobject.functions import ParametricCurve\nfrom manimlib.mobject.geometry import Arrow\nfrom manimlib.mobject.geometry import DashedLine\nfrom manimlib.mobject.geometry import Line\nfrom manimlib.mobject.geometry import Rectangle\nfrom manimlib.mobject.number_line import NumberLine\nfrom manimlib.mobject.svg.tex_mobject import Tex\nfrom manimlib.mobject.types.dot_cloud import DotCloud\nfrom manimlib.mobject.types.surface import ParametricSurface\nfrom manimlib.mobject.types.vectorized_mobject import VGroup\nfrom manimlib.mobject.types.vectorized_mobject import VMobject\nfrom manimlib.utils.bezier import inverse_interpolate\nfrom manimlib.utils.dict_ops import merge_dicts_recursively\nfrom manimlib.utils.simple_functions import binary_search\nfrom manimlib.utils.space_ops import angle_of_vector\nfrom manimlib.utils.space_ops import get_norm\nfrom manimlib.utils.space_ops import rotate_vector\nfrom manimlib.utils.space_ops import normalize\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable, Iterable, Sequence, Type, TypeVar, Optional\n    from manimlib.mobject.mobject import Mobject\n    from manimlib.typing import ManimColor, Vect3, Vect3Array, VectN, RangeSpecifier, Self\n\n    T = TypeVar(\"T\", bound=Mobject)\n\n\nEPSILON = 1e-8\nDEFAULT_X_RANGE = (-8.0, 8.0, 1.0)\nDEFAULT_Y_RANGE = (-4.0, 4.0, 1.0)\n\n\ndef full_range_specifier(range_args):\n    if len(range_args) == 2:\n        return (*range_args, 1)\n    return range_args\n\n\nclass CoordinateSystem(ABC):\n    \"\"\"\n    Abstract class for Axes and NumberPlane\n    \"\"\"\n    dimension: int = 2\n\n    def __init__(\n        self,\n        x_range: RangeSpecifier = DEFAULT_X_RANGE,\n        y_range: RangeSpecifier = DEFAULT_Y_RANGE,\n        num_sampled_graph_points_per_tick: int = 5,\n    ):\n        self.x_range = full_range_specifier(x_range)\n        self.y_range = full_range_specifier(y_range)\n        self.num_sampled_graph_points_per_tick = num_sampled_graph_points_per_tick\n\n    @abstractmethod\n    def coords_to_point(self, *coords: float | VectN) -> Vect3 | Vect3Array:\n        raise Exception(\"Not implemented\")\n\n    @abstractmethod\n    def point_to_coords(self, point: Vect3 | Vect3Array) -> tuple[float | VectN, ...]:\n        raise Exception(\"Not implemented\")\n\n    def c2p(self, *coords: float) -> Vect3 | Vect3Array:\n        \"\"\"Abbreviation for coords_to_point\"\"\"\n        return self.coords_to_point(*coords)\n\n    def p2c(self, point: Vect3) -> tuple[float | VectN, ...]:\n        \"\"\"Abbreviation for point_to_coords\"\"\"\n        return self.point_to_coords(point)\n\n    def get_origin(self) -> Vect3:\n        return self.c2p(*[0] * self.dimension)\n\n    @abstractmethod\n    def get_axes(self) -> VGroup:\n        raise Exception(\"Not implemented\")\n\n    @abstractmethod\n    def get_all_ranges(self) -> list[np.ndarray]:\n        raise Exception(\"Not implemented\")\n\n    def get_axis(self, index: int) -> NumberLine:\n        return self.get_axes()[index]\n\n    def get_x_axis(self) -> NumberLine:\n        return self.get_axis(0)\n\n    def get_y_axis(self) -> NumberLine:\n        return self.get_axis(1)\n\n    def get_z_axis(self) -> NumberLine:\n        return self.get_axis(2)\n\n    def get_x_axis_label(\n        self,\n        label_tex: str,\n        edge: Vect3 = RIGHT,\n        direction: Vect3 = DL,\n        **kwargs\n    ) -> Tex:\n        return self.get_axis_label(\n            label_tex, self.get_x_axis(),\n            edge, direction, **kwargs\n        )\n\n    def get_y_axis_label(\n        self,\n        label_tex: str,\n        edge: Vect3 = UP,\n        direction: Vect3 = DR,\n        **kwargs\n    ) -> Tex:\n        return self.get_axis_label(\n            label_tex, self.get_y_axis(),\n            edge, direction, **kwargs\n        )\n\n    def get_axis_label(\n        self,\n        label_tex: str,\n        axis: Vect3,\n        edge: Vect3,\n        direction: Vect3,\n        buff: float = MED_SMALL_BUFF,\n        ensure_on_screen: bool = False,\n        **kwargs\n    ) -> Tex:\n        label = Tex(label_tex, **kwargs)\n        label.next_to(\n            axis.get_edge_center(edge), direction,\n            buff=buff\n        )\n        if ensure_on_screen:\n            label.shift_onto_screen(buff=MED_SMALL_BUFF)\n        return label\n\n    def get_axis_labels(\n        self,\n        x_label_tex: str = \"x\",\n        y_label_tex: str = \"y\",\n        **kwargs\n    ) -> VGroup:\n        self.axis_labels = VGroup(\n            self.get_x_axis_label(x_label_tex, **kwargs),\n            self.get_y_axis_label(y_label_tex, **kwargs),\n        )\n        return self.axis_labels\n\n    def get_line_from_axis_to_point(\n        self, \n        index: int,\n        point: Vect3,\n        line_func: Type[T] = DashedLine,\n        color: ManimColor = GREY_A,\n        stroke_width: float = 2\n    ) -> T:\n        axis = self.get_axis(index)\n        line = line_func(axis.get_projection(point), point)\n        line.set_stroke(color, stroke_width)\n        return line\n\n    def get_v_line(self, point: Vect3, **kwargs):\n        return self.get_line_from_axis_to_point(0, point, **kwargs)\n\n    def get_h_line(self, point: Vect3, **kwargs):\n        return self.get_line_from_axis_to_point(1, point, **kwargs)\n\n    # Useful for graphing\n    def get_graph(\n        self,\n        function: Callable[[float], float],\n        x_range: Sequence[float] | None = None,\n        bind: bool = False,\n        **kwargs\n    ) -> ParametricCurve:\n        x_range = x_range or self.x_range\n        t_range = np.ones(3)\n        t_range[:len(x_range)] = x_range\n        # For axes, the third coordinate of x_range indicates\n        # tick frequency.  But for functions, it indicates a\n        # sample frequency\n        t_range[2] /= self.num_sampled_graph_points_per_tick\n\n        def parametric_function(t: float) -> Vect3:\n            return self.c2p(t, function(t))\n\n        graph = ParametricCurve(\n            parametric_function,\n            t_range=tuple(t_range),\n            **kwargs\n        )\n        graph.underlying_function = function\n        graph.x_range = x_range\n\n        if bind:\n            self.bind_graph_to_func(graph, function)\n\n        return graph\n\n    def get_parametric_curve(\n        self,\n        function: Callable[[float], Vect3],\n        **kwargs\n    ) -> ParametricCurve:\n        dim = self.dimension\n        graph = ParametricCurve(\n            lambda t: self.coords_to_point(*function(t)[:dim]),\n            **kwargs\n        )\n        graph.underlying_function = function\n        return graph\n\n    def input_to_graph_point(\n        self,\n        x: float,\n        graph: ParametricCurve\n    ) -> Vect3 | None:\n        if hasattr(graph, \"underlying_function\"):\n            return self.coords_to_point(x, graph.underlying_function(x))\n        else:\n            alpha = binary_search(\n                function=lambda a: self.point_to_coords(\n                    graph.quick_point_from_proportion(a)\n                )[0],\n                target=x,\n                lower_bound=self.x_range[0],\n                upper_bound=self.x_range[1],\n            )\n            if alpha is not None:\n                return graph.quick_point_from_proportion(alpha)\n            else:\n                return None\n\n    def i2gp(self, x: float, graph: ParametricCurve) -> Vect3 | None:\n        \"\"\"\n        Alias for input_to_graph_point\n        \"\"\"\n        return self.input_to_graph_point(x, graph)\n\n    def bind_graph_to_func(\n        self,\n        graph: VMobject,\n        func: Callable[[VectN], VectN],\n        jagged: bool = False,\n        get_discontinuities: Optional[Callable[[], Vect3]] = None\n    ) -> VMobject:\n        \"\"\"\n        Use for graphing functions which might change over time, or change with\n        conditions\n        \"\"\"\n        x_values = np.array([self.x_axis.p2n(p) for p in graph.get_points()])\n\n        def get_graph_points():\n            xs = x_values\n            if get_discontinuities:\n                ds = get_discontinuities()\n                ep = 1e-6\n                added_xs = it.chain(*((d - ep, d + ep) for d in ds))\n                xs[:] = sorted([*x_values, *added_xs])[:len(x_values)]\n            return self.c2p(xs, func(xs))\n\n        graph.add_updater(\n            lambda g: g.set_points_as_corners(get_graph_points())\n        )\n        if not jagged:\n            graph.add_updater(lambda g: g.make_smooth(approx=True))\n        return graph\n\n    def get_graph_label(\n        self,\n        graph: ParametricCurve,\n        label: str | Mobject = \"f(x)\",\n        x: float | None = None,\n        direction: Vect3 = RIGHT,\n        buff: float = MED_SMALL_BUFF,\n        color: ManimColor | None = None\n    ) -> Tex | Mobject:\n        if isinstance(label, str):\n            label = Tex(label)\n        if color is None:\n            label.match_color(graph)\n        if x is None:\n            # Searching from the right, find a point\n            # whose y value is in bounds\n            max_y = FRAME_Y_RADIUS - label.get_height()\n            max_x = FRAME_X_RADIUS - label.get_width()\n            for x0 in np.arange(*self.x_range)[::-1]:\n                pt = self.i2gp(x0, graph)\n                if abs(pt[0]) < max_x and abs(pt[1]) < max_y:\n                    x = x0\n                    break\n            if x is None:\n                x = self.x_range[1]\n\n        point = self.input_to_graph_point(x, graph)\n        angle = self.angle_of_tangent(x, graph)\n        normal = rotate_vector(RIGHT, angle + 90 * DEG)\n        if normal[1] < 0:\n            normal *= -1\n        label.next_to(point, normal, buff=buff)\n        label.shift_onto_screen()\n        return label\n\n    def get_v_line_to_graph(self, x: float, graph: ParametricCurve, **kwargs):\n        return self.get_v_line(self.i2gp(x, graph), **kwargs)\n\n    def get_h_line_to_graph(self, x: float, graph: ParametricCurve, **kwargs):\n        return self.get_h_line(self.i2gp(x, graph), **kwargs)\n\n    def get_scatterplot(self,\n                        x_values: Vect3Array,\n                        y_values: Vect3Array,\n                        **dot_config):\n        return DotCloud(self.c2p(x_values, y_values), **dot_config)\n\n    # For calculus\n    def angle_of_tangent(\n        self,\n        x: float,\n        graph: ParametricCurve,\n        dx: float = EPSILON\n    ) -> float:\n        p0 = self.input_to_graph_point(x, graph)\n        p1 = self.input_to_graph_point(x + dx, graph)\n        return angle_of_vector(p1 - p0)\n\n    def slope_of_tangent(\n        self,\n        x: float,\n        graph: ParametricCurve,\n        **kwargs\n    ) -> float:\n        return np.tan(self.angle_of_tangent(x, graph, **kwargs))\n\n    def get_tangent_line(\n        self,\n        x: float,\n        graph: ParametricCurve,\n        length: float = 5,\n        line_func: Type[T] = Line\n    ) -> T:\n        line = line_func(LEFT, RIGHT)\n        line.set_width(length)\n        line.rotate(self.angle_of_tangent(x, graph))\n        line.move_to(self.input_to_graph_point(x, graph))\n        return line\n\n    def get_riemann_rectangles(\n        self,\n        graph: ParametricCurve,\n        x_range: Sequence[float] = None,\n        dx: float | None = None,\n        input_sample_type: str = \"left\",\n        stroke_width: float = 1,\n        stroke_color: ManimColor = BLACK,\n        fill_opacity: float = 1,\n        colors: Iterable[ManimColor] = (BLUE, GREEN),\n        negative_color: ManimColor = RED,\n        stroke_background: bool = True,\n        show_signed_area: bool = True\n    ) -> VGroup:\n        if x_range is None:\n            x_range = self.x_range[:2]\n        if dx is None:\n            dx = self.x_range[2]\n        if len(x_range) < 3:\n            x_range = [*x_range, dx]\n\n        rects = []\n        x_range[1] = x_range[1] + dx\n        xs = np.arange(*x_range)\n        for x0, x1 in zip(xs, xs[1:]):\n            if input_sample_type == \"left\":\n                sample = x0\n            elif input_sample_type == \"right\":\n                sample = x1\n            elif input_sample_type == \"center\":\n                sample = 0.5 * x0 + 0.5 * x1\n            else:\n                raise Exception(\"Invalid input sample type\")\n            height_vect = self.i2gp(sample, graph) - self.c2p(sample, 0)\n            rect = Rectangle(\n                width=self.x_axis.n2p(x1)[0] - self.x_axis.n2p(x0)[0],\n                height=get_norm(height_vect),\n            )\n            rect.positive = height_vect[1] > 0\n            rect.move_to(self.c2p(x0, 0), DL if rect.positive else UL)\n            rects.append(rect)\n        result = VGroup(*rects)\n        result.set_submobject_colors_by_gradient(*colors)\n        result.set_style(\n            stroke_width=stroke_width,\n            stroke_color=stroke_color,\n            fill_opacity=fill_opacity,\n            stroke_behind=stroke_background\n        )\n        for rect in result:\n            if not rect.positive:\n                rect.set_fill(negative_color)\n        return result\n\n    def get_area_under_graph(self, graph, x_range=None, fill_color=BLUE, fill_opacity=0.5):\n        if x_range is None:\n            x_range = [\n                self.x_axis.p2n(graph.get_start()),\n                self.x_axis.p2n(graph.get_end()),\n            ]\n\n        alpha_bounds = [\n            inverse_interpolate(*graph.x_range[:2], x)\n            for x in x_range\n        ]\n        sub_graph = graph.copy()\n        sub_graph.clear_updaters()\n        sub_graph.pointwise_become_partial(graph, *alpha_bounds)\n        sub_graph.add_line_to(self.c2p(x_range[1], 0))\n        sub_graph.add_line_to(self.c2p(x_range[0], 0))\n        sub_graph.add_line_to(sub_graph.get_start())\n\n        sub_graph.set_stroke(width=0)\n        sub_graph.set_fill(fill_color, fill_opacity)\n\n        return sub_graph\n\n\nclass Axes(VGroup, CoordinateSystem):\n    default_axis_config: dict = dict()\n    default_x_axis_config: dict = dict()\n    default_y_axis_config: dict = dict(line_to_number_direction=LEFT)\n\n    def __init__(\n        self,\n        x_range: RangeSpecifier = DEFAULT_X_RANGE,\n        y_range: RangeSpecifier = DEFAULT_Y_RANGE,\n        axis_config: dict = dict(),\n        x_axis_config: dict = dict(),\n        y_axis_config: dict = dict(),\n        height: float | None = None,\n        width: float | None = None,\n        unit_size: float = 1.0,\n        **kwargs\n    ):\n        CoordinateSystem.__init__(self, x_range, y_range, **kwargs)\n        kwargs.pop(\"num_sampled_graph_points_per_tick\", None)\n        VGroup.__init__(self, **kwargs)\n\n        axis_config = dict(**axis_config, unit_size=unit_size)\n        self.x_axis = self.create_axis(\n            self.x_range,\n            axis_config=merge_dicts_recursively(\n                self.default_axis_config,\n                self.default_x_axis_config,\n                axis_config,\n                x_axis_config\n            ),\n            length=width,\n        )\n        self.y_axis = self.create_axis(\n            self.y_range,\n            axis_config=merge_dicts_recursively(\n                self.default_axis_config,\n                self.default_y_axis_config,\n                axis_config,\n                y_axis_config\n            ),\n            length=height,\n        )\n        self.y_axis.rotate(90 * DEG, about_point=ORIGIN)\n        # Add as a separate group in case various other\n        # mobjects are added to self, as for example in\n        # NumberPlane below\n        self.axes = VGroup(self.x_axis, self.y_axis)\n        self.add(*self.axes)\n        self.center()\n\n    def create_axis(\n        self,\n        range_terms: RangeSpecifier,\n        axis_config: dict,\n        length: float | None\n    ) -> NumberLine:\n        axis = NumberLine(range_terms, width=length, **axis_config)\n        axis.shift(-axis.n2p(0))\n        return axis\n\n    def coords_to_point(self, *coords: float | VectN) -> Vect3 | Vect3Array:\n        origin = self.x_axis.number_to_point(0)\n        return origin + sum(\n            axis.number_to_point(coord) - origin\n            for axis, coord in zip(self.get_axes(), coords)\n        )\n\n    def point_to_coords(self, point: Vect3 | Vect3Array) -> tuple[float | VectN, ...]:\n        return tuple([\n            axis.point_to_number(point)\n            for axis in self.get_axes()\n        ])\n\n    def get_axes(self) -> VGroup:\n        return self.axes\n\n    def get_all_ranges(self) -> list[Sequence[float]]:\n        return [self.x_range, self.y_range]\n\n    def add_coordinate_labels(\n        self,\n        x_values: Iterable[float] | None = None,\n        y_values: Iterable[float] | None = None,\n        excluding: Iterable[float] = [0],\n        **kwargs\n    ) -> VGroup:\n        axes = self.get_axes()\n        self.coordinate_labels = VGroup()\n        for axis, values in zip(axes, [x_values, y_values]):\n            labels = axis.add_numbers(values, excluding=excluding, **kwargs)\n            self.coordinate_labels.add(labels)\n        return self.coordinate_labels\n\n\nclass ThreeDAxes(Axes):\n    dimension: int = 3\n    default_z_axis_config: dict = dict()\n\n    def __init__(\n        self,\n        x_range: RangeSpecifier = (-6.0, 6.0, 1.0),\n        y_range: RangeSpecifier = (-5.0, 5.0, 1.0),\n        z_range: RangeSpecifier = (-4.0, 4.0, 1.0),\n        z_axis_config: dict = dict(),\n        z_normal: Vect3 = DOWN,\n        depth: float | None = None,\n        **kwargs\n    ):\n        Axes.__init__(self, x_range, y_range, **kwargs)\n\n        self.z_range = full_range_specifier(z_range)\n        self.z_axis = self.create_axis(\n            self.z_range,\n            axis_config=merge_dicts_recursively(\n                self.default_axis_config,\n                self.default_z_axis_config,\n                kwargs.get(\"axis_config\", {}),\n                z_axis_config\n            ),\n            length=depth,\n        )\n        self.z_axis.rotate(-PI / 2, UP, about_point=ORIGIN)\n        self.z_axis.rotate(\n            angle_of_vector(z_normal), OUT,\n            about_point=ORIGIN\n        )\n        self.z_axis.shift(self.x_axis.n2p(0))\n        self.axes.add(self.z_axis)\n        self.add(self.z_axis)\n\n    def get_all_ranges(self) -> list[Sequence[float]]:\n        return [self.x_range, self.y_range, self.z_range]\n\n    def add_axis_labels(self, x_tex=\"x\", y_tex=\"y\", z_tex=\"z\", font_size=24, buff=0.2):\n        x_label, y_label, z_label = labels = VGroup(*(\n            Tex(tex, font_size=font_size)\n            for tex in [x_tex, y_tex, z_tex]\n        ))\n        z_label.rotate(PI / 2, RIGHT)\n        for label, axis in zip(labels, self):\n            label.next_to(axis, normalize(np.round(axis.get_vector()), 2), buff=buff)\n            axis.add(label)\n        self.axis_labels = labels\n\n    def get_graph(\n        self,\n        func,\n        color=BLUE_E,\n        opacity=0.9,\n        u_range=None,\n        v_range=None,\n        **kwargs\n    ) -> ParametricSurface:\n        xu = self.x_axis.get_unit_size()\n        yu = self.y_axis.get_unit_size()\n        zu = self.z_axis.get_unit_size()\n        x0, y0, z0 = self.get_origin()\n        u_range = u_range or self.x_range[:2]\n        v_range = v_range or self.y_range[:2]\n        return ParametricSurface(\n            lambda u, v: [xu * u + x0, yu * v + y0, zu * func(u, v) + z0],\n            u_range=u_range,\n            v_range=v_range,\n            color=color,\n            opacity=opacity,\n            **kwargs\n        )\n\n    def get_parametric_surface(\n        self,\n        func,\n        color=BLUE_E,\n        opacity=0.9,\n        **kwargs\n    ) -> ParametricSurface:\n        surface = ParametricSurface(func, color=color, opacity=opacity, **kwargs)\n        axes = [self.x_axis, self.y_axis, self.z_axis]\n        for dim, axis in zip(range(3), axes):\n            surface.stretch(axis.get_unit_size(), dim, about_point=ORIGIN)\n        surface.shift(self.get_origin())\n        return surface\n\n\nclass NumberPlane(Axes):\n    default_axis_config: dict = dict(\n        stroke_color=DEFAULT_MOBJECT_COLOR,\n        stroke_width=2,\n        include_ticks=False,\n        include_tip=False,\n        line_to_number_buff=SMALL_BUFF,\n        line_to_number_direction=DL,\n    )\n    default_y_axis_config: dict = dict(\n        line_to_number_direction=DL,\n    )\n\n    def __init__(\n        self,\n        x_range: RangeSpecifier = (-8.0, 8.0, 1.0),\n        y_range: RangeSpecifier = (-4.0, 4.0, 1.0),\n        background_line_style: dict = dict(\n            stroke_color=BLUE_D,\n            stroke_width=2,\n            stroke_opacity=1,\n        ),\n        # Defaults to a faded version of line_config\n        faded_line_style: dict = dict(\n            stroke_width=1,\n            stroke_opacity=0.25,\n        ),\n        faded_line_ratio: int = 4,\n        make_smooth_after_applying_functions: bool = True,\n        **kwargs\n    ):\n        super().__init__(x_range, y_range, **kwargs)\n        self.background_line_style = dict(background_line_style)\n        self.faded_line_style = dict(faded_line_style)\n        self.faded_line_ratio = faded_line_ratio\n        self.make_smooth_after_applying_functions = make_smooth_after_applying_functions\n        self.init_background_lines()\n\n    def init_background_lines(self) -> None:\n        if \"stroke_color\" not in self.faded_line_style:\n            self.faded_line_style[\"stroke_color\"] = self.background_line_style[\"stroke_color\"]\n\n        self.background_lines, self.faded_lines = self.get_lines()\n        self.background_lines.set_style(**self.background_line_style)\n        self.faded_lines.set_style(**self.faded_line_style)\n        self.add_to_back(\n            self.faded_lines,\n            self.background_lines,\n        )\n\n    def get_lines(self) -> tuple[VGroup, VGroup]:\n        x_axis = self.get_x_axis()\n        y_axis = self.get_y_axis()\n\n        x_lines1, x_lines2 = self.get_lines_parallel_to_axis(x_axis, y_axis)\n        y_lines1, y_lines2 = self.get_lines_parallel_to_axis(y_axis, x_axis)\n        lines1 = VGroup(*x_lines1, *y_lines1)\n        lines2 = VGroup(*x_lines2, *y_lines2)\n        return lines1, lines2\n\n    def get_lines_parallel_to_axis(\n        self,\n        axis1: NumberLine,\n        axis2: NumberLine\n    ) -> tuple[VGroup, VGroup]:\n        freq = axis2.x_step\n        ratio = self.faded_line_ratio\n        line = Line(axis1.get_start(), axis1.get_end())\n        dense_freq = (1 + ratio)\n        step = (1 / dense_freq) * freq\n\n        lines1 = VGroup()\n        lines2 = VGroup()\n        inputs = np.arange(axis2.x_min, axis2.x_max + step, step)\n        for i, x in enumerate(inputs):\n            if abs(x) < 1e-8:\n                continue\n            new_line = line.copy()\n            new_line.shift(axis2.n2p(x) - axis2.n2p(0))\n            if i % (1 + ratio) == 0:\n                lines1.add(new_line)\n            else:\n                lines2.add(new_line)\n        return lines1, lines2\n\n    def get_x_unit_size(self) -> float:\n        return self.get_x_axis().get_unit_size()\n\n    def get_y_unit_size(self) -> list:\n        return self.get_x_axis().get_unit_size()\n\n    def get_axes(self) -> VGroup:\n        return self.axes\n\n    def get_vector(self, coords: Iterable[float], **kwargs) -> Arrow:\n        kwargs[\"buff\"] = 0\n        return Arrow(self.c2p(0, 0), self.c2p(*coords), **kwargs)\n\n    def prepare_for_nonlinear_transform(self, num_inserted_curves: int = 50) -> Self:\n        for mob in self.family_members_with_points():\n            num_curves = mob.get_num_curves()\n            if num_inserted_curves > num_curves:\n                mob.insert_n_curves(num_inserted_curves - num_curves)\n            mob.make_smooth_after_applying_functions = True\n        return self\n\n\nclass ComplexPlane(NumberPlane):\n    def number_to_point(self, number: complex | float | np.array) -> Vect3:\n        return self.coords_to_point(np.real(number), np.imag(number))\n\n    def n2p(self, number: complex | float | np.array) -> Vect3:\n        return self.number_to_point(number)\n\n    def point_to_number(self, point: Vect3) -> complex:\n        x, y = self.point_to_coords(point)\n        return complex(x, y)\n\n    def p2n(self, point: Vect3) -> complex:\n        return self.point_to_number(point)\n\n    def get_unit_size(self) -> float:\n        return self.x_axis.get_unit_size()\n\n    def get_default_coordinate_values(\n        self,\n        skip_first: bool = True\n    ) -> list[complex]:\n        x_numbers = self.get_x_axis().get_tick_range()[1:]\n        y_numbers = self.get_y_axis().get_tick_range()[1:]\n        y_numbers = [complex(0, y) for y in y_numbers if y != 0]\n        return [*x_numbers, *y_numbers]\n\n    def add_coordinate_labels(\n        self,\n        numbers: list[complex] | None = None,\n        skip_first: bool = True,\n        font_size: int = 36,\n        **kwargs\n    ) -> Self:\n        if numbers is None:\n            numbers = self.get_default_coordinate_values(skip_first)\n\n        self.coordinate_labels = VGroup()\n        for number in numbers:\n            z = complex(number)\n            if abs(z.imag) > abs(z.real):\n                axis = self.get_y_axis()\n                value = z.imag\n                kwargs[\"unit_tex\"] = \"i\"\n            else:\n                axis = self.get_x_axis()\n                value = z.real\n            number_mob = axis.get_number_mobject(value, font_size=font_size, **kwargs)\n            self.coordinate_labels.add(number_mob)\n        self.add(self.coordinate_labels)\n        return self\n"
  },
  {
    "path": "manimlib/mobject/frame.py",
    "content": "from __future__ import annotations\n\nfrom manimlib.constants import BLACK, GREY_E\nfrom manimlib.constants import FRAME_HEIGHT\nfrom manimlib.mobject.geometry import Rectangle\n\nfrom typing import TYPE_CHECKING\nif TYPE_CHECKING:\n    from manimlib.typing import ManimColor\n\n\nclass ScreenRectangle(Rectangle):\n    def __init__(\n        self,\n        aspect_ratio: float = 16.0 / 9.0,\n        height: float = 4,\n        **kwargs\n    ):\n        super().__init__(\n            width=aspect_ratio * height,\n            height=height,\n            **kwargs\n        )\n\n\nclass FullScreenRectangle(ScreenRectangle):\n    def __init__(\n        self,\n        height: float = FRAME_HEIGHT,\n        fill_color: ManimColor = GREY_E,\n        fill_opacity: float = 1,\n        stroke_width: float = 0,\n        **kwargs,\n    ):\n        super().__init__(\n            height=height,\n            fill_color=fill_color,\n            fill_opacity=fill_opacity,\n            stroke_width=stroke_width,\n            **kwargs\n        )\n\n\nclass FullScreenFadeRectangle(FullScreenRectangle):\n    def __init__(\n        self,\n        stroke_width: float = 0.0,\n        fill_color: ManimColor = BLACK,\n        fill_opacity: float = 0.7,\n        **kwargs,\n    ):\n        super().__init__(\n            stroke_width=stroke_width,\n            fill_color=fill_color,\n            fill_opacity=fill_opacity,\n        )\n"
  },
  {
    "path": "manimlib/mobject/functions.py",
    "content": "from __future__ import annotations\n\nfrom isosurfaces import plot_isoline\nimport numpy as np\n\nfrom manimlib.constants import FRAME_X_RADIUS, FRAME_Y_RADIUS\nfrom manimlib.constants import YELLOW\nfrom manimlib.mobject.types.vectorized_mobject import VMobject\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable, Sequence, Tuple\n    from manimlib.typing import ManimColor, Vect3\n\n\nclass ParametricCurve(VMobject):\n    def __init__(\n        self,\n        t_func: Callable[[float], Sequence[float] | Vect3],\n        t_range: Tuple[float, float, float] = (0, 1, 0.1),\n        epsilon: float = 1e-8,\n        # TODO, automatically figure out discontinuities\n        discontinuities: Sequence[float] = [],\n        use_smoothing: bool = True,\n        **kwargs\n    ):\n        self.t_func = t_func\n        self.t_range = t_range\n        self.epsilon = epsilon\n        self.discontinuities = discontinuities\n        self.use_smoothing = use_smoothing\n        super().__init__(**kwargs)\n\n    def get_point_from_function(self, t: float) -> Vect3:\n        return np.array(self.t_func(t))\n\n    def init_points(self):\n        t_min, t_max, step = self.t_range\n\n        jumps = np.array(self.discontinuities)\n        jumps = jumps[(jumps > t_min) & (jumps < t_max)]\n        boundary_times = [t_min, t_max, *(jumps - self.epsilon), *(jumps + self.epsilon)]\n        boundary_times.sort()\n        for t1, t2 in zip(boundary_times[0::2], boundary_times[1::2]):\n            t_range = [*np.arange(t1, t2, step), t2]\n            points = np.array([self.t_func(t) for t in t_range])\n            self.start_new_path(points[0])\n            self.add_points_as_corners(points[1:])\n        if self.use_smoothing:\n            self.make_smooth(approx=True)\n        if not self.has_points():\n            self.set_points(np.array([self.t_func(t_min)]))\n        return self\n\n    def get_t_func(self):\n        return self.t_func\n\n    def get_function(self):\n        if hasattr(self, \"underlying_function\"):\n            return self.underlying_function\n        if hasattr(self, \"function\"):\n            return self.function\n\n    def get_x_range(self):\n        if hasattr(self, \"x_range\"):\n            return self.x_range\n\n\nclass FunctionGraph(ParametricCurve):\n    def __init__(\n        self,\n        function: Callable[[float], float],\n        x_range: Tuple[float, float, float] = (-8, 8, 0.25),\n        color: ManimColor = YELLOW,\n        **kwargs\n    ):\n        self.function = function\n        self.x_range = x_range\n\n        def parametric_function(t):\n            return [t, function(t), 0]\n\n        super().__init__(parametric_function, self.x_range, **kwargs)\n\n\nclass ImplicitFunction(VMobject):\n    def __init__(\n        self,\n        func: Callable[[float, float], float],\n        x_range: Tuple[float, float] = (-FRAME_X_RADIUS, FRAME_X_RADIUS),\n        y_range: Tuple[float, float] = (-FRAME_Y_RADIUS, FRAME_Y_RADIUS),\n        min_depth: int = 5,\n        max_quads: int = 1500,\n        use_smoothing: bool = False,\n        joint_type: str = 'no_joint',\n        **kwargs\n    ):\n        super().__init__(joint_type=joint_type, **kwargs)\n\n        p_min, p_max = (\n            np.array([x_range[0], y_range[0]]),\n            np.array([x_range[1], y_range[1]]),\n        )\n        curves = plot_isoline(\n            fn=lambda u: func(u[0], u[1]),\n            pmin=p_min,\n            pmax=p_max,\n            min_depth=min_depth,\n            max_quads=max_quads,\n        )  # returns a list of lists of 2D points\n        curves = [\n            np.pad(curve, [(0, 0), (0, 1)])\n            for curve in curves\n            if curve != []\n        ]  # add z coord as 0\n        for curve in curves:\n            self.start_new_path(curve[0])\n            self.add_points_as_corners(curve[1:])\n        if use_smoothing:\n            self.make_smooth()\n"
  },
  {
    "path": "manimlib/mobject/geometry.py",
    "content": "from __future__ import annotations\n\nimport math\n\nimport numpy as np\n\nfrom manimlib.constants import DL, DOWN, DR, LEFT, ORIGIN, OUT, RIGHT, UL, UP, UR\nfrom manimlib.constants import RED, BLACK, DEFAULT_MOBJECT_COLOR, DEFAULT_LIGHT_COLOR\nfrom manimlib.constants import MED_SMALL_BUFF, SMALL_BUFF\nfrom manimlib.constants import DEG, PI, TAU\nfrom manimlib.mobject.mobject import Mobject\nfrom manimlib.mobject.types.vectorized_mobject import DashedVMobject\nfrom manimlib.mobject.types.vectorized_mobject import VGroup\nfrom manimlib.mobject.types.vectorized_mobject import VMobject\nfrom manimlib.utils.bezier import quadratic_bezier_points_for_arc\nfrom manimlib.utils.iterables import adjacent_n_tuples\nfrom manimlib.utils.iterables import adjacent_pairs\nfrom manimlib.utils.simple_functions import clip\nfrom manimlib.utils.simple_functions import fdiv\nfrom manimlib.utils.space_ops import angle_between_vectors\nfrom manimlib.utils.space_ops import angle_of_vector\nfrom manimlib.utils.space_ops import cross2d\nfrom manimlib.utils.space_ops import compass_directions\nfrom manimlib.utils.space_ops import find_intersection\nfrom manimlib.utils.space_ops import get_norm\nfrom manimlib.utils.space_ops import normalize\nfrom manimlib.utils.space_ops import rotate_vector\nfrom manimlib.utils.space_ops import rotation_matrix_transpose\nfrom manimlib.utils.space_ops import rotation_between_vectors\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Iterable, Optional, Tuple\n    from manimlib.typing import ManimColor, Vect3, Vect3Array, Self\n\n\nDEFAULT_DOT_RADIUS = 0.08\nDEFAULT_SMALL_DOT_RADIUS = 0.04\nDEFAULT_DASH_LENGTH = 0.05\nDEFAULT_ARROW_TIP_LENGTH = 0.35\nDEFAULT_ARROW_TIP_WIDTH = 0.35\n\n\n# Deprecate?\nclass TipableVMobject(VMobject):\n    \"\"\"\n    Meant for shared functionality between Arc and Line.\n    Functionality can be classified broadly into these groups:\n\n        * Adding, Creating, Modifying tips\n            - add_tip calls create_tip, before pushing the new tip\n                into the TipableVMobject's list of submobjects\n            - stylistic and positional configuration\n\n        * Checking for tips\n            - Boolean checks for whether the TipableVMobject has a tip\n                and a starting tip\n\n        * Getters\n            - Straightforward accessors, returning information pertaining\n                to the TipableVMobject instance's tip(s), its length etc\n    \"\"\"\n    tip_config: dict = dict(\n        fill_opacity=1.0,\n        stroke_width=0.0,\n        tip_style=0.0,  # triangle=0, inner_smooth=1, dot=2\n    )\n\n    # Adding, Creating, Modifying tips\n    def add_tip(self, at_start: bool = False, **kwargs) -> Self:\n        \"\"\"\n        Adds a tip to the TipableVMobject instance, recognising\n        that the endpoints might need to be switched if it's\n        a 'starting tip' or not.\n        \"\"\"\n        tip = self.create_tip(at_start, **kwargs)\n        self.reset_endpoints_based_on_tip(tip, at_start)\n        self.asign_tip_attr(tip, at_start)\n        tip.set_color(self.get_stroke_color())\n        self.add(tip)\n        return self\n\n    def create_tip(self, at_start: bool = False, **kwargs) -> ArrowTip:\n        \"\"\"\n        Stylises the tip, positions it spacially, and returns\n        the newly instantiated tip to the caller.\n        \"\"\"\n        tip = self.get_unpositioned_tip(**kwargs)\n        self.position_tip(tip, at_start)\n        return tip\n\n    def get_unpositioned_tip(self, **kwargs) -> ArrowTip:\n        \"\"\"\n        Returns a tip that has been stylistically configured,\n        but has not yet been given a position in space.\n        \"\"\"\n        config = dict()\n        config.update(self.tip_config)\n        config.update(kwargs)\n        return ArrowTip(**config)\n\n    def position_tip(self, tip: ArrowTip, at_start: bool = False) -> ArrowTip:\n        # Last two control points, defining both\n        # the end, and the tangency direction\n        if at_start:\n            anchor = self.get_start()\n            handle = self.get_first_handle()\n        else:\n            handle = self.get_last_handle()\n            anchor = self.get_end()\n        tip.rotate(angle_of_vector(handle - anchor) - PI - tip.get_angle())\n        tip.shift(anchor - tip.get_tip_point())\n        return tip\n\n    def reset_endpoints_based_on_tip(self, tip: ArrowTip, at_start: bool) -> Self:\n        if self.get_length() == 0:\n            # Zero length, put_start_and_end_on wouldn't\n            # work\n            return self\n\n        if at_start:\n            start = tip.get_base()\n            end = self.get_end()\n        else:\n            start = self.get_start()\n            end = tip.get_base()\n        self.put_start_and_end_on(start, end)\n        return self\n\n    def asign_tip_attr(self, tip: ArrowTip, at_start: bool) -> Self:\n        if at_start:\n            self.start_tip = tip\n        else:\n            self.tip = tip\n        return self\n\n    # Checking for tips\n    def has_tip(self) -> bool:\n        return hasattr(self, \"tip\") and self.tip in self\n\n    def has_start_tip(self) -> bool:\n        return hasattr(self, \"start_tip\") and self.start_tip in self\n\n    # Getters\n    def pop_tips(self) -> VGroup:\n        start, end = self.get_start_and_end()\n        result = VGroup()\n        if self.has_tip():\n            result.add(self.tip)\n            self.remove(self.tip)\n        if self.has_start_tip():\n            result.add(self.start_tip)\n            self.remove(self.start_tip)\n        self.put_start_and_end_on(start, end)\n        return result\n\n    def get_tips(self) -> VGroup:\n        \"\"\"\n        Returns a VGroup (collection of VMobjects) containing\n        the TipableVMObject instance's tips.\n        \"\"\"\n        result = VGroup()\n        if hasattr(self, \"tip\"):\n            result.add(self.tip)\n        if hasattr(self, \"start_tip\"):\n            result.add(self.start_tip)\n        return result\n\n    def get_tip(self) -> ArrowTip:\n        \"\"\"Returns the TipableVMobject instance's (first) tip,\n        otherwise throws an exception.\"\"\"\n        tips = self.get_tips()\n        if len(tips) == 0:\n            raise Exception(\"tip not found\")\n        else:\n            return tips[0]\n\n    def get_default_tip_length(self) -> float:\n        return self.tip_length\n\n    def get_first_handle(self) -> Vect3:\n        return self.get_points()[1]\n\n    def get_last_handle(self) -> Vect3:\n        return self.get_points()[-2]\n\n    def get_end(self) -> Vect3:\n        if self.has_tip():\n            return self.tip.get_start()\n        else:\n            return VMobject.get_end(self)\n\n    def get_start(self) -> Vect3:\n        if self.has_start_tip():\n            return self.start_tip.get_start()\n        else:\n            return VMobject.get_start(self)\n\n    def get_length(self) -> float:\n        start, end = self.get_start_and_end()\n        return get_norm(start - end)\n\n\nclass Arc(TipableVMobject):\n    '''\n    Creates an arc.\n    Parameters\n    -----\n    start_angle : float\n        Starting angle of the arc in radians. (Angles are measured counter-clockwise)\n    angle : float\n        Angle subtended by the arc at its center in radians. (Angles are measured counter-clockwise)\n    radius : float\n        Radius of the arc\n    arc_center : array_like\n        Center of the arc\n    Examples :\n            arc = Arc(start_angle=TAU/4, angle=TAU/2, radius=3, arc_center=ORIGIN)\n            arc = Arc(angle=TAU/4, radius=4.5, arc_center=(1,2,0), color=BLUE)\n    Returns\n    -----\n    out : Arc object\n        An Arc object satisfying the specified parameters\n    '''\n\n    def __init__(\n        self,\n        start_angle: float = 0,\n        angle: float = TAU / 4,\n        radius: float = 1.0,\n        n_components: Optional[int] = None,\n        arc_center: Vect3 = ORIGIN,\n        **kwargs\n    ):\n        super().__init__(**kwargs)\n\n        if n_components is None:\n            # 16 components for a full circle\n            n_components = int(15 * (abs(angle) / TAU)) + 1\n\n        self.set_points(quadratic_bezier_points_for_arc(angle, n_components))\n        self.rotate(start_angle, about_point=ORIGIN)\n        self.scale(radius, about_point=ORIGIN)\n        self.shift(arc_center)\n\n    def get_arc_center(self) -> Vect3:\n        \"\"\"\n        Looks at the normals to the first two\n        anchors, and finds their intersection points\n        \"\"\"\n        # First two anchors and handles\n        a1, h, a2 = self.get_points()[:3]\n        # Tangent vectors\n        t1 = h - a1\n        t2 = h - a2\n        # Normals\n        n1 = rotate_vector(t1, TAU / 4)\n        n2 = rotate_vector(t2, TAU / 4)\n        return find_intersection(a1, n1, a2, n2)\n\n    def get_start_angle(self) -> float:\n        angle = angle_of_vector(self.get_start() - self.get_arc_center())\n        return angle % TAU\n\n    def get_stop_angle(self) -> float:\n        angle = angle_of_vector(self.get_end() - self.get_arc_center())\n        return angle % TAU\n\n    def move_arc_center_to(self, point: Vect3) -> Self:\n        self.shift(point - self.get_arc_center())\n        return self\n\n\nclass ArcBetweenPoints(Arc):\n    '''\n    Creates an arc passing through the specified points with \"angle\" as the\n    angle subtended at its center.\n    Parameters\n    -----\n    start : array_like\n        Starting point of the arc\n    end : array_like\n        Ending point of the arc\n    angle : float\n        Angle subtended by the arc at its center in radians. (Angles are measured counter-clockwise)\n    Examples :\n            arc = ArcBetweenPoints(start=(0, 0, 0), end=(1, 2, 0), angle=TAU / 2)\n            arc = ArcBetweenPoints(start=(-2, 3, 0), end=(1, 2, 0), angle=-TAU / 12, color=BLUE)\n    Returns\n    -----\n    out : ArcBetweenPoints object\n        An ArcBetweenPoints object satisfying the specified parameters\n    '''\n\n    def __init__(\n        self,\n        start: Vect3,\n        end: Vect3,\n        angle: float = TAU / 4,\n        **kwargs\n    ):\n        super().__init__(angle=angle, **kwargs)\n        if angle == 0:\n            self.set_points_as_corners([LEFT, RIGHT])\n        self.put_start_and_end_on(start, end)\n\n\nclass CurvedArrow(ArcBetweenPoints):\n    '''\n    Creates a curved arrow passing through the specified points with \"angle\" as the\n    angle subtended at its center.\n    Parameters\n    -----\n    start_point : array_like\n        Starting point of the curved arrow\n    end_point : array_like\n        Ending point of the curved arrow\n    angle : float\n        Angle subtended by the curved arrow at its center in radians. (Angles are measured counter-clockwise)\n    Examples :\n            curvedArrow = CurvedArrow(start_point=(0, 0, 0), end_point=(1, 2, 0), angle=TAU/2)\n            curvedArrow = CurvedArrow(start_point=(-2, 3, 0), end_point=(1, 2, 0), angle=-TAU/12, color=BLUE)\n    Returns\n    -----\n    out : CurvedArrow object\n        A CurvedArrow object satisfying the specified parameters\n    '''\n\n    def __init__(\n        self,\n        start_point: Vect3,\n        end_point: Vect3,\n        **kwargs\n    ):\n        super().__init__(start_point, end_point, **kwargs)\n        self.add_tip()\n\n\nclass CurvedDoubleArrow(CurvedArrow):\n    '''\n    Creates a curved double arrow passing through the specified points with \"angle\" as the\n    angle subtended at its center.\n    Parameters\n    -----\n    start_point : array_like\n        Starting point of the curved double arrow\n    end_point : array_like\n        Ending point of the curved double arrow\n    angle : float\n        Angle subtended by the curved double arrow at its center in radians. (Angles are measured counter-clockwise)\n    Examples :\n            curvedDoubleArrow = CurvedDoubleArrow(start_point = (0, 0, 0), end_point = (1, 2, 0), angle = TAU/2)\n            curvedDoubleArrow = CurvedDoubleArrow(start_point = (-2, 3, 0), end_point = (1, 2, 0), angle = -TAU/12, color = BLUE)\n    Returns\n    -----\n    out : CurvedDoubleArrow object\n        A CurvedDoubleArrow object satisfying the specified parameters\n    '''\n\n    def __init__(\n        self,\n        start_point: Vect3,\n        end_point: Vect3,\n        **kwargs\n    ):\n        super().__init__(start_point, end_point, **kwargs)\n        self.add_tip(at_start=True)\n\n\nclass Circle(Arc):\n    '''\n    Creates a circle.\n    Parameters\n    -----\n    radius : float\n        Radius of the circle\n    arc_center : array_like\n        Center of the circle\n    Examples :\n            circle = Circle(radius=2, arc_center=(1,2,0))\n            circle = Circle(radius=3.14, arc_center=2 * LEFT + UP, color=DARK_BLUE)\n    Returns\n    -----\n    out : Circle object\n        A Circle object satisfying the specified parameters\n    '''\n\n    def __init__(\n        self,\n        start_angle: float = 0,\n        stroke_color: ManimColor = RED,\n        **kwargs\n    ):\n        super().__init__(\n            start_angle, TAU,\n            stroke_color=stroke_color,\n            **kwargs\n        )\n\n    def surround(\n        self,\n        mobject: Mobject,\n        dim_to_match: int = 0,\n        stretch: bool = False,\n        buff: float = MED_SMALL_BUFF\n    ) -> Self:\n        self.replace(mobject, dim_to_match, stretch)\n        self.stretch((self.get_width() + 2 * buff) / self.get_width(), 0)\n        self.stretch((self.get_height() + 2 * buff) / self.get_height(), 1)\n        return self\n\n    def point_at_angle(self, angle: float) -> Vect3:\n        start_angle = self.get_start_angle()\n        return self.point_from_proportion(\n            ((angle - start_angle) % TAU) / TAU\n        )\n\n    def get_radius(self) -> float:\n        return get_norm(self.get_start() - self.get_center())\n\n\nclass Dot(Circle):\n    '''\n    Creates a dot. Dot is a filled white circle with no bounary and DEFAULT_DOT_RADIUS.\n    Parameters\n    -----\n    point : array_like\n        Coordinates of center of the dot.\n    Examples :\n            dot = Dot(point=(1, 2, 0))\n\n    Returns\n    -----\n    out : Dot object\n        A Dot object satisfying the specified parameters\n    '''\n\n    def __init__(\n        self,\n        point: Vect3 = ORIGIN,\n        radius: float = DEFAULT_DOT_RADIUS,\n        stroke_color: ManimColor = BLACK,\n        stroke_width: float = 0.0,\n        fill_opacity: float = 1.0,\n        fill_color: ManimColor = DEFAULT_MOBJECT_COLOR,\n        **kwargs\n    ):\n        super().__init__(\n            arc_center=point,\n            radius=radius,\n            stroke_color=stroke_color,\n            stroke_width=stroke_width,\n            fill_opacity=fill_opacity,\n            fill_color=fill_color,\n            **kwargs\n        )\n\n\nclass SmallDot(Dot):\n    '''\n    Creates a small dot. Small dot is a filled white circle with no bounary and DEFAULT_SMALL_DOT_RADIUS.\n    Parameters\n    -----\n    point : array_like\n        Coordinates of center of the small dot.\n    Examples :\n            smallDot = SmallDot(point=(1, 2, 0))\n\n    Returns\n    -----\n    out : SmallDot object\n        A SmallDot object satisfying the specified parameters\n    '''\n\n    def __init__(\n        self,\n        point: Vect3 = ORIGIN,\n        radius: float = DEFAULT_SMALL_DOT_RADIUS,\n        **kwargs\n    ):\n        super().__init__(point, radius=radius, **kwargs)\n\n\nclass Ellipse(Circle):\n    '''\n    Creates an ellipse.\n    Parameters\n    -----\n    width : float\n        Width of the ellipse\n    height : float\n        Height of the ellipse\n    arc_center : array_like\n        Coordinates of center of the ellipse\n    Examples :\n            ellipse = Ellipse(width=4, height=1, arc_center=(3, 3, 0))\n            ellipse = Ellipse(width=2, height=5, arc_center=ORIGIN, color=BLUE)\n    Returns\n    -----\n    out : Ellipse object\n        An Ellipse object satisfying the specified parameters\n    '''\n\n    def __init__(\n        self,\n        width: float = 2.0,\n        height: float = 1.0,\n        **kwargs\n    ):\n        super().__init__(**kwargs)\n        self.set_width(width, stretch=True)\n        self.set_height(height, stretch=True)\n\n\nclass AnnularSector(VMobject):\n    '''\n    Creates an annular sector.\n    Parameters\n    -----\n    inner_radius : float\n        Inner radius of the annular sector\n    outer_radius : float\n        Outer radius of the annular sector\n    start_angle : float\n        Starting angle of the annular sector (Angles are measured counter-clockwise)\n    angle : float\n        Angle subtended at the center of the annular sector (Angles are measured counter-clockwise)\n    arc_center : array_like\n        Coordinates of center of the annular sector\n    Examples :\n            annularSector = AnnularSector(inner_radius=1, outer_radius=2, angle=TAU/2, start_angle=TAU*3/4, arc_center=(1,-2,0))\n    Returns\n    -----\n    out : AnnularSector object\n        An AnnularSector object satisfying the specified parameters\n    '''\n\n    def __init__(\n        self,\n        angle: float = TAU / 4,\n        start_angle: float = 0.0,\n        inner_radius: float = 1.0,\n        outer_radius: float = 2.0,\n        arc_center: Vect3 = ORIGIN,\n        fill_color: ManimColor = DEFAULT_LIGHT_COLOR,\n        fill_opacity: float = 1.0,\n        stroke_width: float = 0.0,\n        **kwargs,\n    ):\n        super().__init__(\n            fill_color=fill_color,\n            fill_opacity=fill_opacity,\n            stroke_width=stroke_width,\n            **kwargs,\n        )\n\n        # Initialize points\n        inner_arc, outer_arc = [\n            Arc(\n                start_angle=start_angle,\n                angle=angle,\n                radius=radius,\n                arc_center=arc_center,\n            )\n            for radius in (inner_radius, outer_radius)\n        ]\n        self.set_points(inner_arc.get_points()[::-1])  # Reverse\n        self.add_line_to(outer_arc.get_points()[0])\n        self.add_subpath(outer_arc.get_points())\n        self.add_line_to(inner_arc.get_points()[-1])\n\n\nclass Sector(AnnularSector):\n    '''\n    Creates a sector.\n    Parameters\n    -----\n    outer_radius : float\n        Radius of the sector\n    start_angle : float\n        Starting angle of the sector in radians. (Angles are measured counter-clockwise)\n    angle : float\n        Angle subtended by the sector at its center in radians. (Angles are measured counter-clockwise)\n    arc_center : array_like\n        Coordinates of center of the sector\n    Examples :\n            sector = Sector(outer_radius=1, start_angle=TAU/3, angle=TAU/2, arc_center=[0,3,0])\n            sector = Sector(outer_radius=3, start_angle=TAU/4, angle=TAU/4, arc_center=ORIGIN, color=PINK)\n    Returns\n    -----\n    out : Sector object\n        An Sector object satisfying the specified parameters\n    '''\n\n    def __init__(\n        self,\n        angle: float = TAU / 4,\n        radius: float = 1.0,\n        **kwargs\n    ):\n        super().__init__(\n            angle,\n            inner_radius=0,\n            outer_radius=radius,\n            **kwargs\n        )\n\n\nclass Annulus(VMobject):\n    '''\n    Creates an annulus.\n    Parameters\n    -----\n    inner_radius : float\n        Inner radius of the annulus\n    outer_radius : float\n        Outer radius of the annulus\n    arc_center : array_like\n        Coordinates of center of the annulus\n    Examples :\n            annulus = Annulus(inner_radius=2, outer_radius=3, arc_center=(1, -1, 0))\n            annulus = Annulus(inner_radius=2, outer_radius=3, stroke_width=20, stroke_color=RED, fill_color=BLUE, arc_center=ORIGIN)\n    Returns\n    -----\n    out : Annulus object\n        An Annulus object satisfying the specified parameters\n    '''\n\n    def __init__(\n        self,\n        inner_radius: float = 1.0,\n        outer_radius: float = 2.0,\n        fill_opacity: float = 1.0,\n        stroke_width: float = 0.0,\n        fill_color: ManimColor = DEFAULT_LIGHT_COLOR,\n        center: Vect3 = ORIGIN,\n        **kwargs,\n    ):\n        super().__init__(\n            fill_color=fill_color,\n            fill_opacity=fill_opacity,\n            stroke_width=stroke_width,\n            **kwargs,\n        )\n\n        self.radius = outer_radius\n        outer_path = outer_radius * quadratic_bezier_points_for_arc(TAU)\n        inner_path = inner_radius * quadratic_bezier_points_for_arc(-TAU)\n        self.add_subpath(outer_path)\n        self.add_subpath(inner_path)\n        self.shift(center)\n\n\nclass Line(TipableVMobject):\n    '''\n    Creates a line joining the points \"start\" and \"end\".\n    Parameters\n    -----\n    start : array_like\n        Starting point of the line\n    end : array_like\n        Ending point of the line\n    Examples :\n            line = Line((0, 0, 0), (3, 0, 0))\n            line = Line((1, 2, 0), (-2, -3, 0), color=BLUE)\n    Returns\n    -----\n    out : Line object\n        A Line object satisfying the specified parameters\n    '''\n\n    def __init__(\n        self,\n        start: Vect3 | Mobject = LEFT,\n        end: Vect3 | Mobject = RIGHT,\n        buff: float = 0.0,\n        path_arc: float = 0.0,\n        **kwargs\n    ):\n        super().__init__(**kwargs)\n        self.path_arc = path_arc\n        self.buff = buff\n        self.set_start_and_end_attrs(start, end)\n        self.set_points_by_ends(self.start, self.end, buff, path_arc)\n\n    def set_points_by_ends(\n        self,\n        start: Vect3,\n        end: Vect3,\n        buff: float = 0,\n        path_arc: float = 0\n    ) -> Self:\n        self.clear_points()\n        self.start_new_path(start)\n        self.add_arc_to(end, path_arc)\n\n        # Apply buffer\n        if buff > 0:\n            length = self.get_arc_length()\n            alpha = min(buff / length, 0.5)\n            self.pointwise_become_partial(self, alpha, 1 - alpha)\n        return self\n    \n    def reset_points_around_ends(self) -> Self:\n        self.set_points_by_ends(\n            self.get_start().copy(),\n            self.get_end().copy(),\n            path_arc=self.path_arc\n        )\n        return self\n\n    def set_path_arc(self, path_arc: float) -> Self:\n        self.path_arc = path_arc\n        self.reset_points_around_ends()\n        return self\n\n    def set_start_and_end_attrs(self, start: Vect3 | Mobject, end: Vect3 | Mobject):\n        # If either start or end are Mobjects, this\n        # gives their centers\n        rough_start = self.pointify(start)\n        rough_end = self.pointify(end)\n        vect = normalize(rough_end - rough_start)\n        # Now that we know the direction between them,\n        # we can find the appropriate boundary point from\n        # start and end, if they're mobjects\n        self.start = self.pointify(start, vect)\n        self.end = self.pointify(end, -vect)\n\n    def pointify(\n        self,\n        mob_or_point: Mobject | Vect3,\n        direction: Vect3 | None = None\n    ) -> Vect3:\n        \"\"\"\n        Take an argument passed into Line (or subclass) and turn\n        it into a 3d point.\n        \"\"\"\n        if isinstance(mob_or_point, Mobject):\n            mob = mob_or_point\n            if direction is None:\n                return mob.get_center()\n            else:\n                return mob.get_continuous_bounding_box_point(direction)\n        else:\n            point = mob_or_point\n            result = np.zeros(self.dim)\n            result[:len(point)] = point\n            return result\n\n    def put_start_and_end_on(self, start: Vect3, end: Vect3) -> Self:\n        curr_start, curr_end = self.get_start_and_end()\n        if np.isclose(curr_start, curr_end).all():\n            # Handle null lines more gracefully\n            self.set_points_by_ends(start, end, buff=0, path_arc=self.path_arc)\n            return self\n        return super().put_start_and_end_on(start, end)\n\n    def get_vector(self) -> Vect3:\n        return self.get_end() - self.get_start()\n\n    def get_unit_vector(self) -> Vect3:\n        return normalize(self.get_vector())\n\n    def get_angle(self) -> float:\n        return angle_of_vector(self.get_vector())\n\n    def get_projection(self, point: Vect3) -> Vect3:\n        \"\"\"\n        Return projection of a point onto the line\n        \"\"\"\n        unit_vect = self.get_unit_vector()\n        start = self.get_start()\n        return start + np.dot(point - start, unit_vect) * unit_vect\n\n    def get_slope(self) -> float:\n        return np.tan(self.get_angle())\n\n    def set_angle(self, angle: float, about_point: Optional[Vect3] = None) -> Self:\n        if about_point is None:\n            about_point = self.get_start()\n        self.rotate(\n            angle - self.get_angle(),\n            about_point=about_point,\n        )\n        return self\n\n    def set_length(self, length: float, **kwargs):\n        self.scale(length / self.get_length(), **kwargs)\n        return self\n\n    def get_arc_length(self) -> float:\n        arc_len = get_norm(self.get_vector())\n        if self.path_arc > 0:\n            arc_len *= self.path_arc / (2 * math.sin(self.path_arc / 2))\n        return arc_len\n\n    def set_perpendicular_to_camera(self, camera_frame):\n        to_cam = camera_frame.get_implied_camera_location() - self.get_center()\n        normal = self.get_unit_normal()\n        axis = normalize(self.get_vector())\n        # Project to be perpendicular to axis\n        trg_normal = to_cam - np.dot(to_cam, axis) * axis\n        mat = rotation_between_vectors(normal, trg_normal)\n        self.apply_matrix(mat, about_point=self.get_start())\n        return self\n\nclass DashedLine(Line):\n    '''\n    Creates a dashed line joining the points \"start\" and \"end\".\n    Parameters\n    -----\n    start : array_like\n        Starting point of the dashed line\n    end : array_like\n        Ending point of the dashed line\n    dash_length : float\n        length of each dash\n    Examples :\n            line = DashedLine((0, 0, 0), (3, 0, 0))\n            line = DashedLine((1, 2, 3), (4, 5, 6), dash_length=0.01)\n    Returns\n    -----\n    out : DashedLine object\n        A DashedLine object satisfying the specified parameters\n    '''\n\n    def __init__(\n        self,\n        start: Vect3 = LEFT,\n        end: Vect3 = RIGHT,\n        dash_length: float = DEFAULT_DASH_LENGTH,\n        positive_space_ratio: float = 0.5,\n        **kwargs\n    ):\n        super().__init__(start, end, **kwargs)\n\n        num_dashes = self.calculate_num_dashes(dash_length, positive_space_ratio)\n        dashes = DashedVMobject(\n            self,\n            num_dashes=num_dashes,\n            positive_space_ratio=positive_space_ratio\n        )\n        self.clear_points()\n        self.add(*dashes)\n\n    def calculate_num_dashes(self, dash_length: float, positive_space_ratio: float) -> int:\n        try:\n            full_length = dash_length / positive_space_ratio\n            return int(np.ceil(self.get_length() / full_length))\n        except ZeroDivisionError:\n            return 1\n\n    def get_start(self) -> Vect3:\n        if len(self.submobjects) > 0:\n            return self.submobjects[0].get_start()\n        else:\n            return Line.get_start(self)\n\n    def get_end(self) -> Vect3:\n        if len(self.submobjects) > 0:\n            return self.submobjects[-1].get_end()\n        else:\n            return Line.get_end(self)\n\n    def get_start_and_end(self) -> Tuple[Vect3, Vect3]:\n        return self.get_start(), self.get_end()\n\n    def get_first_handle(self) -> Vect3:\n        return self.submobjects[0].get_points()[1]\n\n    def get_last_handle(self) -> Vect3:\n        return self.submobjects[-1].get_points()[-2]\n\n\nclass TangentLine(Line):\n    '''\n    Creates a tangent line to the specified vectorized math object.\n    Parameters\n    -----\n    vmob : VMobject object\n        Vectorized math object which the line will be tangent to\n    alpha : float\n        Point on the perimeter of the vectorized math object. It takes value between 0 and 1\n        both inclusive.\n    length : float\n        Length of the tangent line\n    Examples :\n            circle = Circle(arc_center=ORIGIN, radius=3, color=GREEN)\n            tangentLine = TangentLine(vmob=circle, alpha=1/3, length=6, color=BLUE)\n    Returns\n    -----\n    out : TangentLine object\n        A TangentLine object satisfying the specified parameters\n    '''\n\n    def __init__(\n        self,\n        vmob: VMobject,\n        alpha: float,\n        length: float = 2,\n        d_alpha: float = 1e-6,\n        **kwargs\n    ):\n        a1 = clip(alpha - d_alpha, 0, 1)\n        a2 = clip(alpha + d_alpha, 0, 1)\n        super().__init__(vmob.pfp(a1), vmob.pfp(a2), **kwargs)\n        self.scale(length / self.get_length())\n\n\nclass Elbow(VMobject):\n    '''\n    Creates an elbow. Elbow is an L-shaped shaped object.\n    Parameters\n    -----\n    width : float\n        Width of the elbow\n    angle : float\n        Angle of the elbow in radians with the horizontal. (Angles are measured counter-clockwise)\n    Examples :\n            line = Elbow(width=2, angle=TAU/16)\n    Returns\n    -----\n    out : Elbow object\n        A Elbow object satisfying the specified parameters\n    '''\n\n    def __init__(\n        self,\n        width: float = 0.2,\n        angle: float = 0,\n        **kwargs\n    ):\n        super().__init__(**kwargs)\n        self.set_points_as_corners([UP, UR, RIGHT])\n        self.set_width(width, about_point=ORIGIN)\n        self.rotate(angle, about_point=ORIGIN)\n\n\nclass StrokeArrow(Line):\n    def __init__(\n        self,\n        start: Vect3 | Mobject,\n        end: Vect3 | Mobject,\n        stroke_color: ManimColor = DEFAULT_LIGHT_COLOR,\n        stroke_width: float = 5,\n        buff: float = 0.25,\n        tip_width_ratio: float = 5,\n        tip_len_to_width: float = 0.0075,\n        max_tip_length_to_length_ratio: float = 0.3,\n        max_width_to_length_ratio: float = 8.0,\n        **kwargs,\n    ):\n        self.tip_width_ratio = tip_width_ratio\n        self.tip_len_to_width = tip_len_to_width\n        self.max_tip_length_to_length_ratio = max_tip_length_to_length_ratio\n        self.max_width_to_length_ratio = max_width_to_length_ratio\n        self.n_tip_points = 3\n        self.original_stroke_width = stroke_width\n        super().__init__(\n            start, end,\n            stroke_color=stroke_color,\n            stroke_width=stroke_width,\n            buff=buff,\n            **kwargs\n        )\n\n    def set_points_by_ends(\n        self,\n        start: Vect3,\n        end: Vect3,\n        buff: float = 0,\n        path_arc: float = 0\n    ) -> Self:\n        super().set_points_by_ends(start, end, buff, path_arc)\n        self.insert_tip_anchor()\n        self.create_tip_with_stroke_width()\n        return self\n\n    def insert_tip_anchor(self) -> Self:\n        prev_end = self.get_end()\n        arc_len = self.get_arc_length()\n        tip_len = self.get_stroke_width() * self.tip_width_ratio * self.tip_len_to_width\n        if tip_len >= self.max_tip_length_to_length_ratio * arc_len:\n            alpha = self.max_tip_length_to_length_ratio\n        else:\n            alpha = tip_len / arc_len\n        self.pointwise_become_partial(self, 0.0, 1.0 - alpha)\n        # Dumb that this is needed\n        self.start_new_path(self.point_from_proportion(1 - 1e-5))\n        self.add_line_to(prev_end)\n        self.n_tip_points = 3\n        return self\n\n    @Mobject.affects_data\n    def create_tip_with_stroke_width(self) -> Self:\n        if self.get_num_points() < 3:\n            return self\n        tip_width = self.tip_width_ratio * min(\n            float(self.original_stroke_width),\n            self.max_width_to_length_ratio * self.get_length(),\n        )\n        ntp = self.n_tip_points\n        self.data['stroke_width'][:-ntp] = self.data['stroke_width'][0]\n        self.data['stroke_width'][-ntp:, 0] = tip_width * np.linspace(1, 0, ntp)\n        return self\n\n    def reset_tip(self) -> Self:\n        self.set_points_by_ends(\n            self.get_start(), self.get_end(),\n            path_arc=self.path_arc\n        )\n        return self\n\n    def set_stroke(\n        self,\n        color: ManimColor | Iterable[ManimColor] | None = None,\n        width: float | Iterable[float] | None = None,\n        *args, **kwargs\n    ) -> Self:\n        super().set_stroke(color=color, width=width, *args, **kwargs)\n        self.original_stroke_width = self.get_stroke_width()\n        if self.has_points():\n            self.reset_tip()\n        return self\n\n    def _handle_scale_side_effects(self, scale_factor: float) -> Self:\n        if scale_factor != 1.0:\n            self.reset_tip()\n        return self\n\n\nclass Arrow(Line):\n    '''\n    Creates an arrow.\n\n    Parameters\n    ----------\n    start : array_like\n        Starting point of the arrow\n    end : array_like\n        Ending point of the arrow \n    buff : float, optional\n        Buffer distance from the start and end points. Default is MED_SMALL_BUFF.\n    path_arc : float, optional\n        If set to a non-zero value, the arrow will be curved to subtend a circle by this angle.\n        Default is 0 (straight arrow).\n    thickness : float, optional\n        How wide should the base of the arrow be. This affects the shaft width. Default is 3.0.\n    tip_width_ratio : float, optional\n        Ratio of the tip width to the shaft width. Default is 5.\n    tip_angle : float, optional\n        Angle of the arrow tip in radians. Default is PI/3 (60 degrees).\n    max_tip_length_to_length_ratio : float, optional\n        Maximum ratio of tip length to total arrow length. Prevents tips from being too large\n        relative to the arrow. Default is 0.5.\n    max_width_to_length_ratio : float, optional\n        Maximum ratio of arrow width to total arrow length. Prevents arrows from being too wide\n        relative to their length. Default is 0.1.\n    **kwargs\n        Additional keyword arguments passed to the parent Line class.\n\n    Examples\n    --------\n    >>> arrow = Arrow((0, 0, 0), (3, 0, 0))\n    >>> curved_arrow = Arrow(LEFT, RIGHT, path_arc=PI/4)\n    >>> thick_arrow = Arrow(UP, DOWN, thickness=5.0, tip_width_ratio=3)\n\n    Returns\n    -------\n    Arrow\n        An Arrow object satisfying the specified parameters.\n    '''\n\n    tickness_multiplier = 0.015\n\n    def __init__(\n        self,\n        start: Vect3 | Mobject = LEFT,\n        end: Vect3 | Mobject = LEFT,\n        buff: float = MED_SMALL_BUFF,\n        path_arc: float = 0,\n        fill_color: ManimColor = DEFAULT_LIGHT_COLOR,\n        fill_opacity: float = 1.0,\n        stroke_width: float = 0.0,\n        thickness: float = 3.0,\n        tip_width_ratio: float = 5,\n        tip_angle: float = PI / 3,\n        max_tip_length_to_length_ratio: float = 0.5,\n        max_width_to_length_ratio: float = 0.1,\n        **kwargs,\n    ):\n        self.thickness = thickness\n        self.tip_width_ratio = tip_width_ratio\n        self.tip_angle = tip_angle\n        self.max_tip_length_to_length_ratio = max_tip_length_to_length_ratio\n        self.max_width_to_length_ratio = max_width_to_length_ratio\n        super().__init__(\n            start, end,\n            fill_color=fill_color,\n            fill_opacity=fill_opacity,\n            stroke_width=stroke_width,\n            buff=buff,\n            path_arc=path_arc,\n            **kwargs\n        )\n\n    def get_key_dimensions(self, length):\n        width = self.thickness * self.tickness_multiplier\n        w_ratio = fdiv(self.max_width_to_length_ratio, fdiv(width, length))\n        if w_ratio < 1:\n            width *= w_ratio\n\n        tip_width = self.tip_width_ratio * width\n        tip_length = tip_width / (2 * np.tan(self.tip_angle / 2))\n        t_ratio = fdiv(self.max_tip_length_to_length_ratio, fdiv(tip_length, length))\n        if t_ratio < 1:\n            tip_length *= t_ratio\n            tip_width *= t_ratio\n\n        return width, tip_width, tip_length\n\n    def set_points_by_ends(\n        self,\n        start: Vect3,\n        end: Vect3,\n        buff: float = 0,\n        path_arc: float = 0\n    ) -> Self:\n        vect = end - start\n        length = max(get_norm(vect), 1e-8)  # More systematic min?\n        unit_vect = normalize(vect)\n\n        # Find the right tip length and thickness\n        width, tip_width, tip_length = self.get_key_dimensions(length - buff)\n\n        # Adjust start and end based on buff\n        if path_arc == 0:\n            start = start + buff * unit_vect\n            end = end - buff * unit_vect\n        else:\n            R = length / 2 / math.sin(path_arc / 2)\n            midpoint = 0.5 * (start + end)\n            center = midpoint + rotate_vector(0.5 * vect, PI / 2) / math.tan(path_arc / 2)\n            sign = 1\n            start = center + rotate_vector(start - center, buff / R)\n            end = center + rotate_vector(end - center, -buff / R)\n            path_arc -= (2 * buff + tip_length) / R\n        vect = end - start\n        length = get_norm(vect)\n\n        # Find points for the stem, imagining an arrow pointed to the left\n        if path_arc == 0:\n            points1 = (length - tip_length) * np.array([RIGHT, 0.5 * RIGHT, ORIGIN])\n            points1 += width * UP / 2\n            points2 = points1[::-1] + width * DOWN\n        else:\n            # Find arc points\n            points1 = quadratic_bezier_points_for_arc(path_arc)\n            points2 = np.array(points1[::-1])\n            points1 *= (R + width / 2)\n            points2 *= (R - width / 2)\n            rot_T = rotation_matrix_transpose(PI / 2 - path_arc, OUT)\n            for points in points1, points2:\n                points[:] = np.dot(points, rot_T)\n                points += R * DOWN\n\n        self.set_points(points1)\n        # Tip\n        self.add_line_to(tip_width * UP / 2)\n        self.add_line_to(tip_length * LEFT)\n        self.tip_index = len(self.get_points()) - 1\n        self.add_line_to(tip_width * DOWN / 2)\n        self.add_line_to(points2[0])\n        # Close it out\n        self.add_subpath(points2)\n        self.add_line_to(points1[0])\n\n        # Reposition to match proper start and end\n        self.rotate(angle_of_vector(vect) - self.get_angle())\n        self.rotate(\n            PI / 2 - np.arccos(normalize(vect)[2]),\n            axis=rotate_vector(self.get_unit_vector(), -PI / 2),\n        )\n        self.shift(start - self.get_start())\n        return self\n\n    def get_start(self) -> Vect3:\n        points = self.get_points()\n        return 0.5 * (points[0] + points[-3])\n\n    def get_end(self) -> Vect3:\n        return self.get_points()[self.tip_index]\n\n    def get_start_and_end(self):\n        return (self.get_start(), self.get_end())\n\n    def put_start_and_end_on(self, start: Vect3, end: Vect3) -> Self:\n        self.set_points_by_ends(start, end, buff=0, path_arc=self.path_arc)\n        return self\n\n    def scale(self, *args, **kwargs) -> Self:\n        super().scale(*args, **kwargs)\n        self.reset_points_around_ends()\n        return self\n\n    def set_thickness(self, thickness: float) -> Self:\n        self.thickness = thickness\n        self.reset_points_around_ends()\n        return self\n\n\nclass Vector(Arrow):\n    '''\n    Creates a vector. Vector is an arrow with start point as ORIGIN\n    Parameters\n    -----\n    direction : array_like\n        Coordinates of direction of the arrow\n    Examples :\n            arrow = Vector(direction=LEFT)\n    Returns\n    -----\n    out : Vector object\n        A Vector object satisfying the specified parameters\n    '''\n\n    def __init__(\n        self,\n        direction: Vect3 = RIGHT,\n        buff: float = 0.0,\n        **kwargs\n    ):\n        if len(direction) == 2:\n            direction = np.hstack([direction, 0])\n        super().__init__(ORIGIN, direction, buff=buff, **kwargs)\n\n\nclass CubicBezier(VMobject):\n    '''\n    Creates a cubic Bézier curve.\n\n    A cubic Bézier curve is defined by four control points: two anchor points (start and end)\n    and two handle points that control the curvature. The curve starts at the first anchor\n    point, is \"pulled\" toward the handle points, and ends at the second anchor point.\n\n    Parameters\n    ----------\n    a0 : array_like\n        First anchor point (starting point of the curve).\n    h0 : array_like\n        First handle point (controls the initial direction and curvature from a0).\n    h1 : array_like\n        Second handle point (controls the final direction and curvature toward a1).\n    a1 : array_like\n        Second anchor point (ending point of the curve).\n    **kwargs\n        Additional keyword arguments passed to the parent VMobject class, such as\n        stroke_color, stroke_width, fill_color, fill_opacity, etc.\n    Returns\n    -------\n    CubicBezier\n        A CubicBezier object representing the specified cubic Bézier curve.\n\n    '''\n\n    def __init__(\n        self,\n        a0: Vect3,\n        h0: Vect3,\n        h1: Vect3,\n        a1: Vect3,\n        **kwargs\n    ):\n        super().__init__(**kwargs)\n        self.add_cubic_bezier_curve(a0, h0, h1, a1)\n\n\nclass Polygon(VMobject):\n    '''\n    Creates a polygon by joining the specified vertices.\n    Parameters\n    -----\n    *vertices : array_like\n        Vertex of the polygon\n    Examples :\n            triangle = Polygon((-3,0,0), (3,0,0), (0,3,0))\n    Returns\n    -----\n    out : Polygon object\n        A Polygon object satisfying the specified parameters\n    '''\n\n    def __init__(\n        self,\n        *vertices: Vect3,\n        **kwargs\n    ):\n        super().__init__(**kwargs)\n        self.set_points_as_corners([*vertices, vertices[0]])\n\n    def get_vertices(self) -> Vect3Array:\n        return self.get_start_anchors()\n\n    def round_corners(self, radius: Optional[float] = None) -> Self:\n        if radius is None:\n            verts = self.get_vertices()\n            min_edge_length = min(\n                get_norm(v1 - v2)\n                for v1, v2 in zip(verts, verts[1:])\n                if not np.isclose(v1, v2).all()\n            )\n            radius = 0.25 * min_edge_length\n        vertices = self.get_vertices()\n        arcs = []\n        for v1, v2, v3 in adjacent_n_tuples(vertices, 3):\n            vect1 = normalize(v2 - v1)\n            vect2 = normalize(v3 - v2)\n            angle = angle_between_vectors(vect1, vect2)\n            # Distance between vertex and start of the arc\n            cut_off_length = radius * np.tan(angle / 2)\n            # Negative radius gives concave curves\n            sign = float(np.sign(radius * cross2d(vect1, vect2)))\n            arc = ArcBetweenPoints(\n                v2 - vect1 * cut_off_length,\n                v2 + vect2 * cut_off_length,\n                angle=sign * angle,\n                n_components=2,\n            )\n            arcs.append(arc)\n\n        self.clear_points()\n        # To ensure that we loop through starting with last\n        arcs = [arcs[-1], *arcs[:-1]]\n        for arc1, arc2 in adjacent_pairs(arcs):\n            self.add_subpath(arc1.get_points())\n            self.add_line_to(arc2.get_start())\n        return self\n\n\nclass Polyline(VMobject):\n    def __init__(\n        self,\n        *vertices: Vect3,\n        **kwargs\n    ):\n        super().__init__(**kwargs)\n        self.set_points_as_corners(vertices)\n\n\nclass RegularPolygon(Polygon):\n    '''\n    Creates a regular polygon of edge length 1 at the center of the screen.\n    Parameters\n    -----\n    n : int\n        Number of vertices of the regular polygon\n    start_angle : float\n        Starting angle of the regular polygon in radians. (Angles are measured counter-clockwise)\n    Examples :\n            pentagon = RegularPolygon(n=5, start_angle=30 * DEGREES)\n    Returns\n    -----\n    out : RegularPolygon object\n        A RegularPolygon object satisfying the specified parameters\n    '''\n\n    def __init__(\n        self,\n        n: int = 6,\n        radius: float = 1.0,\n        start_angle: float | None = None,\n        **kwargs\n    ):\n        # Defaults to 0 for odd, 90 for even\n        if start_angle is None:\n            start_angle = (n % 2) * 90 * DEG\n        start_vect = rotate_vector(radius * RIGHT, start_angle)\n        vertices = compass_directions(n, start_vect)\n        super().__init__(*vertices, **kwargs)\n\n\nclass Triangle(RegularPolygon):\n    '''\n    Creates a triangle of edge length 1 at the center of the screen.\n    Parameters\n    -----\n    start_angle : float\n        Starting angle of the triangle in radians. (Angles are measured counter-clockwise)\n    Examples :\n            triangle = Triangle(start_angle=45 * DEGREES)\n    Returns\n    -----\n    out : Triangle object\n        A Triangle object satisfying the specified parameters\n    '''\n\n    def __init__(self, **kwargs):\n        super().__init__(n=3, **kwargs)\n\n\nclass ArrowTip(Triangle):\n    def __init__(\n        self,\n        angle: float = 0,\n        width: float = DEFAULT_ARROW_TIP_WIDTH,\n        length: float = DEFAULT_ARROW_TIP_LENGTH,\n        fill_opacity: float = 1.0,\n        fill_color: ManimColor = DEFAULT_MOBJECT_COLOR,\n        stroke_width: float = 0.0,\n        tip_style: int = 0,  # triangle=0, inner_smooth=1, dot=2\n        **kwargs\n    ):\n        super().__init__(\n            start_angle=0,\n            fill_opacity=fill_opacity,\n            fill_color=fill_color,\n            stroke_width=stroke_width,\n            **kwargs\n        )\n        self.set_height(width)\n        self.set_width(length, stretch=True)\n        if tip_style == 1:\n            self.set_height(length * 0.9, stretch=True)\n            self.data[\"point\"][4] += np.array([0.6 * length, 0, 0])\n        elif tip_style == 2:\n            h = length / 2\n            self.set_points(Dot().set_width(h).get_points())\n        self.rotate(angle)\n\n    def get_base(self) -> Vect3:\n        return self.point_from_proportion(0.5)\n\n    def get_tip_point(self) -> Vect3:\n        return self.get_points()[0]\n\n    def get_vector(self) -> Vect3:\n        return self.get_tip_point() - self.get_base()\n\n    def get_angle(self) -> float:\n        return angle_of_vector(self.get_vector())\n\n    def get_length(self) -> float:\n        return get_norm(self.get_vector())\n\n\nclass Rectangle(Polygon):\n    '''\n    Creates a rectangle at the center of the screen.\n    Parameters\n    -----\n    width : float\n        Width of the rectangle\n    height : float\n        Height of the rectangle\n    Examples :\n            rectangle = Rectangle(width=3, height=4, color=BLUE)\n    Returns\n    -----\n    out : Rectangle object\n        A Rectangle object satisfying the specified parameters\n    '''\n\n    def __init__(\n        self,\n        width: float = 4.0,\n        height: float = 2.0,\n        **kwargs\n    ):\n        super().__init__(UR, UL, DL, DR, **kwargs)\n        self.set_width(width, stretch=True)\n        self.set_height(height, stretch=True)\n\n    def surround(self, mobject, buff=SMALL_BUFF) -> Self:\n        target_shape = np.array(mobject.get_shape()) + 2 * buff\n        self.set_shape(*target_shape)\n        self.move_to(mobject)\n        return self\n\n\nclass Square(Rectangle):\n    '''\n    Creates a square at the center of the screen.\n    Parameters\n    -----\n    side_length : float\n        Edge length of the square\n    Examples :\n            square = Square(side_length=5, color=PINK)\n    Returns\n    -----\n    out : Square object\n        A Square object satisfying the specified parameters\n    '''\n\n    def __init__(self, side_length: float = 2.0, **kwargs):\n        super().__init__(side_length, side_length, **kwargs)\n\n\nclass RoundedRectangle(Rectangle):\n    '''\n    Creates a rectangle with round edges at the center of the screen.\n    Parameters\n    -----\n    width : float\n        Width of the rounded rectangle\n    height : float\n        Height of the rounded rectangle\n    corner_radius : float\n        Corner radius of the rectangle\n    Examples :\n            rRectangle = RoundedRectangle(width=3, height=4, corner_radius=1, color=BLUE)\n    Returns\n    -----\n    out : RoundedRectangle object\n        A RoundedRectangle object satisfying the specified parameters\n    '''\n\n    def __init__(\n        self,\n        width: float = 4.0,\n        height: float = 2.0,\n        corner_radius: float = 0.5,\n        **kwargs\n    ):\n        super().__init__(width, height, **kwargs)\n        self.round_corners(corner_radius)\n"
  },
  {
    "path": "manimlib/mobject/interactive.py",
    "content": "from __future__ import annotations\n\nimport numpy as np\nfrom pyglet.window import key as PygletWindowKeys\n\nfrom manimlib.constants import FRAME_HEIGHT, FRAME_WIDTH\nfrom manimlib.constants import DOWN, LEFT, ORIGIN, RIGHT, UP\nfrom manimlib.constants import MED_LARGE_BUFF, MED_SMALL_BUFF, SMALL_BUFF\nfrom manimlib.constants import BLACK, BLUE, GREEN, GREY_A, GREY_C, RED, WHITE, DEFAULT_MOBJECT_COLOR\nfrom manimlib.mobject.mobject import Group\nfrom manimlib.mobject.mobject import Mobject\nfrom manimlib.mobject.geometry import Circle\nfrom manimlib.mobject.geometry import Dot\nfrom manimlib.mobject.geometry import Line\nfrom manimlib.mobject.geometry import Rectangle\nfrom manimlib.mobject.geometry import RoundedRectangle\nfrom manimlib.mobject.geometry import Square\nfrom manimlib.mobject.svg.text_mobject import Text\nfrom manimlib.mobject.types.vectorized_mobject import VGroup\nfrom manimlib.mobject.value_tracker import ValueTracker\nfrom manimlib.utils.color import rgb_to_hex\nfrom manimlib.utils.space_ops import get_closest_point_on_line\nfrom manimlib.utils.space_ops import get_norm\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable\n    from manimlib.typing import ManimColor\n\n\n# Interactive Mobjects\n\nclass MotionMobject(Mobject):\n    \"\"\"\n        You could hold and drag this object to any position\n    \"\"\"\n    def __init__(self, mobject: Mobject, **kwargs):\n        super().__init__(**kwargs)\n        assert isinstance(mobject, Mobject)\n        self.mobject = mobject\n        self.mobject.add_mouse_drag_listner(self.mob_on_mouse_drag)\n        # To avoid locking it as static mobject\n        self.mobject.add_updater(lambda mob: None)\n        self.add(mobject)\n\n    def mob_on_mouse_drag(self, mob: Mobject, event_data: dict[str, np.ndarray]) -> bool:\n        mob.move_to(event_data[\"point\"])\n        return False\n\n\nclass Button(Mobject):\n    \"\"\"\n        Pass any mobject and register an on_click method\n\n        The on_click method takes mobject as argument like updater\n    \"\"\"\n\n    def __init__(self, mobject: Mobject, on_click: Callable[[Mobject]], **kwargs):\n        super().__init__(**kwargs)\n        assert isinstance(mobject, Mobject)\n        self.on_click = on_click\n        self.mobject = mobject\n        self.mobject.add_mouse_press_listner(self.mob_on_mouse_press)\n        self.add(self.mobject)\n\n    def mob_on_mouse_press(self, mob: Mobject, event_data) -> bool:\n        self.on_click(mob)\n        return False\n\n\n# Controls\n\nclass ControlMobject(ValueTracker):\n    def __init__(self, value: float, *mobjects: Mobject, **kwargs):\n        super().__init__(value=value, **kwargs)\n        self.add(*mobjects)\n\n        # To avoid lock_static_mobject_data while waiting in scene\n        self.add_updater(lambda mob: None)\n        self.fix_in_frame()\n\n    def set_value(self, value: float):\n        self.assert_value(value)\n        self.set_value_anim(value)\n        return ValueTracker.set_value(self, value)\n\n    def assert_value(self, value):\n        # To be implemented in subclasses\n        pass\n\n    def set_value_anim(self, value):\n        # To be implemented in subclasses\n        pass\n\n\nclass EnableDisableButton(ControlMobject):\n    def __init__(\n        self,\n        value: bool = True,\n        value_type: np.dtype = np.dtype(bool),\n        rect_kwargs: dict = {\n            \"width\": 0.5,\n            \"height\": 0.5,\n            \"fill_opacity\": 1.0\n        },\n        enable_color: ManimColor = GREEN,\n        disable_color: ManimColor = RED,\n        **kwargs\n    ):\n        self.value = value\n        self.value_type = value_type\n        self.rect_kwargs = rect_kwargs\n        self.enable_color = enable_color\n        self.disable_color = disable_color\n\n        self.box = Rectangle(**self.rect_kwargs)\n        super().__init__(value, self.box, **kwargs)\n        self.add_mouse_press_listner(self.on_mouse_press)\n\n    def assert_value(self, value: bool) -> None:\n        assert isinstance(value, bool)\n\n    def set_value_anim(self, value: bool) -> None:\n        if value:\n            self.box.set_fill(self.enable_color)\n        else:\n            self.box.set_fill(self.disable_color)\n\n    def toggle_value(self) -> None:\n        super().set_value(not self.get_value())\n\n    def on_mouse_press(self, mob: Mobject, event_data) -> bool:\n        mob.toggle_value()\n        return False\n\n\nclass Checkbox(ControlMobject):\n    def __init__(\n        self,\n        value: bool = True,\n        value_type: np.dtype = np.dtype(bool),\n        rect_kwargs: dict = {\n            \"width\": 0.5,\n            \"height\": 0.5,\n            \"fill_opacity\": 0.0\n        },\n        checkmark_kwargs: dict = {\n            \"stroke_color\": GREEN,\n            \"stroke_width\": 6,\n        },\n        cross_kwargs: dict = {\n            \"stroke_color\": RED,\n            \"stroke_width\": 6,\n        },\n        box_content_buff: float = SMALL_BUFF,\n        **kwargs\n    ):\n        self.value_type = value_type\n        self.rect_kwargs = rect_kwargs\n        self.checkmark_kwargs = checkmark_kwargs\n        self.cross_kwargs = cross_kwargs\n        self.box_content_buff = box_content_buff\n\n        self.box = Rectangle(**self.rect_kwargs)\n        self.box_content = self.get_checkmark() if value else self.get_cross()\n        super().__init__(value, self.box, self.box_content, **kwargs)\n        self.add_mouse_press_listner(self.on_mouse_press)\n\n    def assert_value(self, value: bool) -> None:\n        assert isinstance(value, bool)\n\n    def toggle_value(self) -> None:\n        super().set_value(not self.get_value())\n\n    def set_value_anim(self, value: bool) -> None:\n        if value:\n            self.box_content.become(self.get_checkmark())\n        else:\n            self.box_content.become(self.get_cross())\n\n    def on_mouse_press(self, mob: Mobject, event_data) -> None:\n        mob.toggle_value()\n        return False\n\n    # Helper methods\n\n    def get_checkmark(self) -> VGroup:\n        checkmark = VGroup(\n            Line(UP / 2 + 2 * LEFT, DOWN + LEFT, **self.checkmark_kwargs),\n            Line(DOWN + LEFT, UP + RIGHT, **self.checkmark_kwargs)\n        )\n\n        checkmark.stretch_to_fit_width(self.box.get_width())\n        checkmark.stretch_to_fit_height(self.box.get_height())\n        checkmark.scale(0.5)\n        checkmark.move_to(self.box)\n        return checkmark\n\n    def get_cross(self) -> VGroup:\n        cross = VGroup(\n            Line(UP + LEFT, DOWN + RIGHT, **self.cross_kwargs),\n            Line(UP + RIGHT, DOWN + LEFT, **self.cross_kwargs)\n        )\n\n        cross.stretch_to_fit_width(self.box.get_width())\n        cross.stretch_to_fit_height(self.box.get_height())\n        cross.scale(0.5)\n        cross.move_to(self.box)\n        return cross\n\n\nclass LinearNumberSlider(ControlMobject):\n    def __init__(\n        self,\n        value: float = 0,\n        value_type: type = np.float64,\n        min_value: float = -10.0,\n        max_value: float = 10.0,\n        step: float = 1.0,\n        rounded_rect_kwargs: dict = {\n            \"height\": 0.075,\n            \"width\": 2,\n            \"corner_radius\": 0.0375\n        },\n        circle_kwargs: dict = {\n            \"radius\": 0.1,\n            \"stroke_color\": GREY_A,\n            \"fill_color\": GREY_A,\n            \"fill_opacity\": 1.0\n        },\n        **kwargs\n    ):\n        self.value_type = value_type\n        self.min_value = min_value\n        self.max_value = max_value\n        self.step = step\n        self.rounded_rect_kwargs = rounded_rect_kwargs\n        self.circle_kwargs = circle_kwargs\n\n        self.bar = RoundedRectangle(**self.rounded_rect_kwargs)\n        self.slider = Circle(**self.circle_kwargs)\n        self.slider_axis = Line(\n            start=self.bar.get_bounding_box_point(LEFT),\n            end=self.bar.get_bounding_box_point(RIGHT)\n        )\n        self.slider_axis.set_opacity(0.0)\n        self.slider.move_to(self.slider_axis)\n\n        self.slider.add_mouse_drag_listner(self.slider_on_mouse_drag)\n\n        super().__init__(value, self.bar, self.slider, self.slider_axis, **kwargs)\n\n    def assert_value(self, value: float) -> None:\n        assert self.min_value <= value <= self.max_value\n\n    def set_value_anim(self, value: float) -> None:\n        prop = (value - self.min_value) / (self.max_value - self.min_value)\n        self.slider.move_to(self.slider_axis.point_from_proportion(prop))\n\n    def slider_on_mouse_drag(self, mob, event_data: dict[str, np.ndarray]) -> bool:\n        self.set_value(self.get_value_from_point(event_data[\"point\"]))\n        return False\n\n    # Helper Methods\n\n    def get_value_from_point(self, point: np.ndarray) -> float:\n        start, end = self.slider_axis.get_start_and_end()\n        point_on_line = get_closest_point_on_line(start, end, point)\n        prop = get_norm(point_on_line - start) / get_norm(end - start)\n        value = self.min_value + prop * (self.max_value - self.min_value)\n        no_of_steps = int((value - self.min_value) / self.step)\n        value_nearest_to_step = self.min_value + no_of_steps * self.step\n        return value_nearest_to_step\n\n\nclass ColorSliders(Group):\n    def __init__(\n        self,\n        sliders_kwargs: dict = {},\n        rect_kwargs: dict = {\n            \"width\": 2.0,\n            \"height\": 0.5,\n            \"stroke_opacity\": 1.0\n        },\n        background_grid_kwargs: dict = {\n            \"colors\": [GREY_A, GREY_C],\n            \"single_square_len\": 0.1\n        },\n        sliders_buff: float = MED_LARGE_BUFF,\n        default_rgb_value: int = 255,\n        default_a_value: int = 1,\n        **kwargs\n    ):\n        self.sliders_kwargs = sliders_kwargs\n        self.rect_kwargs = rect_kwargs\n        self.background_grid_kwargs = background_grid_kwargs\n        self.sliders_buff = sliders_buff\n        self.default_rgb_value = default_rgb_value\n        self.default_a_value = default_a_value\n\n        rgb_kwargs = {\"value\": self.default_rgb_value, \"min_value\": 0, \"max_value\": 255, \"step\": 1}\n        a_kwargs = {\"value\": self.default_a_value, \"min_value\": 0, \"max_value\": 1, \"step\": 0.04}\n\n        self.r_slider = LinearNumberSlider(**self.sliders_kwargs, **rgb_kwargs)\n        self.g_slider = LinearNumberSlider(**self.sliders_kwargs, **rgb_kwargs)\n        self.b_slider = LinearNumberSlider(**self.sliders_kwargs, **rgb_kwargs)\n        self.a_slider = LinearNumberSlider(**self.sliders_kwargs, **a_kwargs)\n        self.sliders = Group(\n            self.r_slider,\n            self.g_slider,\n            self.b_slider,\n            self.a_slider\n        )\n        self.sliders.arrange(DOWN, buff=self.sliders_buff)\n\n        self.r_slider.slider.set_color(RED)\n        self.g_slider.slider.set_color(GREEN)\n        self.b_slider.slider.set_color(BLUE)\n        self.a_slider.slider.set_color_by_gradient(BLACK, WHITE)\n\n        self.selected_color_box = Rectangle(**self.rect_kwargs)\n        self.selected_color_box.add_updater(\n            lambda mob: mob.set_fill(\n                self.get_picked_color(), self.get_picked_opacity()\n            )\n        )\n        self.background = self.get_background()\n\n        super().__init__(\n            Group(self.background, self.selected_color_box).fix_in_frame(),\n            self.sliders,\n            **kwargs\n        )\n\n        self.arrange(DOWN)\n\n    def get_background(self) -> VGroup:\n        single_square_len = self.background_grid_kwargs[\"single_square_len\"]\n        colors = self.background_grid_kwargs[\"colors\"]\n        width = self.rect_kwargs[\"width\"]\n        height = self.rect_kwargs[\"height\"]\n        rows = int(height / single_square_len)\n        cols = int(width / single_square_len)\n        cols = (cols + 1) if (cols % 2 == 0) else cols\n\n        single_square = Square(single_square_len)\n        grid = single_square.get_grid(n_rows=rows, n_cols=cols, buff=0.0)\n        grid.stretch_to_fit_width(width)\n        grid.stretch_to_fit_height(height)\n        grid.move_to(self.selected_color_box)\n\n        for idx, square in enumerate(grid):\n            assert isinstance(square, Square)\n            square.set_stroke(width=0.0, opacity=0.0)\n            square.set_fill(colors[idx % len(colors)], 1.0)\n\n        return grid\n\n    def set_value(self, r: float, g: float, b: float, a: float):\n        self.r_slider.set_value(r)\n        self.g_slider.set_value(g)\n        self.b_slider.set_value(b)\n        self.a_slider.set_value(a)\n\n    def get_value(self) -> np.ndarary:\n        r = self.r_slider.get_value() / 255\n        g = self.g_slider.get_value() / 255\n        b = self.b_slider.get_value() / 255\n        alpha = self.a_slider.get_value()\n        return np.array((r, g, b, alpha))\n\n    def get_picked_color(self) -> str:\n        rgba = self.get_value()\n        return rgb_to_hex(rgba[:3])\n\n    def get_picked_opacity(self) -> float:\n        rgba = self.get_value()\n        return rgba[3]\n\n\nclass Textbox(ControlMobject):\n    def __init__(\n        self,\n        value: str = \"\",\n        value_type: np.dtype = np.dtype(object),\n        box_kwargs: dict = {\n            \"width\": 2.0,\n            \"height\": 1.0,\n            \"fill_color\": DEFAULT_MOBJECT_COLOR,\n            \"fill_opacity\": 1.0,\n        },\n        text_kwargs: dict = {\n            \"color\": BLUE\n        },\n        text_buff: float = MED_SMALL_BUFF,\n        isInitiallyActive: bool = False,\n        active_color: ManimColor = BLUE,\n        deactive_color: ManimColor = RED,\n        **kwargs\n    ):\n        self.value_type = value_type\n        self.box_kwargs = box_kwargs\n        self.text_kwargs = text_kwargs\n        self.text_buff = text_buff\n        self.isInitiallyActive = isInitiallyActive\n        self.active_color = active_color\n        self.deactive_color = deactive_color\n\n        self.isActive = self.isInitiallyActive\n        self.box = Rectangle(**self.box_kwargs)\n        self.box.add_mouse_press_listner(self.box_on_mouse_press)\n        self.text = Text(value, **self.text_kwargs)\n        super().__init__(value, self.box, self.text, **kwargs)\n        self.update_text(value)\n        self.active_anim(self.isActive)\n        self.add_key_press_listner(self.on_key_press)\n\n    def set_value_anim(self, value: str) -> None:\n        self.update_text(value)\n\n    def update_text(self, value: str) -> None:\n        text = self.text\n        self.remove(text)\n        text.__init__(value, **self.text_kwargs)\n        height = text.get_height()\n        text.set_width(self.box.get_width() - 2 * self.text_buff)\n        if text.get_height() > height:\n            text.set_height(height)\n        text.add_updater(lambda mob: mob.move_to(self.box))\n        text.fix_in_frame()\n        self.add(text)\n\n    def active_anim(self, isActive: bool) -> None:\n        if isActive:\n            self.box.set_stroke(self.active_color)\n        else:\n            self.box.set_stroke(self.deactive_color)\n\n    def box_on_mouse_press(self, mob, event_data) -> bool:\n        self.isActive = not self.isActive\n        self.active_anim(self.isActive)\n        return False\n\n    def on_key_press(self, mob: Mobject, event_data: dict[str, int]) -> bool | None:\n        symbol = event_data[\"symbol\"]\n        modifiers = event_data[\"modifiers\"]\n        char = chr(symbol)\n        if mob.isActive:\n            old_value = mob.get_value()\n            new_value = old_value\n            if char.isalnum():\n                if (modifiers & PygletWindowKeys.MOD_SHIFT) or (modifiers & PygletWindowKeys.MOD_CAPSLOCK):\n                    new_value = old_value + char.upper()\n                else:\n                    new_value = old_value + char.lower()\n            elif symbol in [PygletWindowKeys.SPACE]:\n                new_value = old_value + char\n            elif symbol == PygletWindowKeys.TAB:\n                new_value = old_value + '\\t'\n            elif symbol == PygletWindowKeys.BACKSPACE:\n                new_value = old_value[:-1] or ''\n            mob.set_value(new_value)\n            return False\n\n\nclass ControlPanel(Group):\n    def __init__(\n        self,\n        *controls: ControlMobject,\n        panel_kwargs: dict = {\n            \"width\": FRAME_WIDTH / 4,\n            \"height\": MED_SMALL_BUFF + FRAME_HEIGHT,\n            \"fill_color\": GREY_C,\n            \"fill_opacity\": 1.0,\n            \"stroke_width\": 0.0\n        },\n        opener_kwargs: dict = {\n            \"width\": FRAME_WIDTH / 8,\n            \"height\": 0.5,\n            \"fill_color\": GREY_C,\n            \"fill_opacity\": 1.0\n        },\n        opener_text_kwargs: dict = {\n            \"text\": \"Control Panel\",\n            \"font_size\": 20\n        },\n        **kwargs\n    ):\n        self.panel_kwargs = panel_kwargs\n        self.opener_kwargs = opener_kwargs\n        self.opener_text_kwargs = opener_text_kwargs\n\n        self.panel = Rectangle(**self.panel_kwargs)\n        self.panel.to_corner(UP + LEFT, buff=0)\n        self.panel.shift(self.panel.get_height() * UP)\n        self.panel.add_mouse_scroll_listner(self.panel_on_mouse_scroll)\n\n        self.panel_opener_rect = Rectangle(**self.opener_kwargs)\n        self.panel_info_text = Text(**self.opener_text_kwargs)\n        self.panel_info_text.move_to(self.panel_opener_rect)\n\n        self.panel_opener = Group(self.panel_opener_rect, self.panel_info_text)\n        self.panel_opener.next_to(self.panel, DOWN, aligned_edge=DOWN)\n        self.panel_opener.add_mouse_drag_listner(self.panel_opener_on_mouse_drag)\n\n        self.controls = Group(*controls)\n        self.controls.arrange(DOWN, center=False, aligned_edge=ORIGIN)\n        self.controls.move_to(self.panel)\n\n        super().__init__(\n            self.panel, self.panel_opener,\n            self.controls,\n            **kwargs\n        )\n\n        self.move_panel_and_controls_to_panel_opener()\n        self.fix_in_frame()\n\n    def move_panel_and_controls_to_panel_opener(self) -> None:\n        self.panel.next_to(\n            self.panel_opener_rect,\n            direction=UP,\n            buff=0\n        )\n\n        controls_old_x = self.controls.get_x()\n        self.controls.next_to(\n            self.panel_opener_rect,\n            direction=UP,\n            buff=MED_SMALL_BUFF\n        )\n\n        self.controls.set_x(controls_old_x)\n\n    def add_controls(self, *new_controls: ControlMobject) -> None:\n        self.controls.add(*new_controls)\n        self.move_panel_and_controls_to_panel_opener()\n\n    def remove_controls(self, *controls_to_remove: ControlMobject) -> None:\n        self.controls.remove(*controls_to_remove)\n        self.move_panel_and_controls_to_panel_opener()\n\n    def open_panel(self):\n        panel_opener_x = self.panel_opener.get_x()\n        self.panel_opener.to_corner(DOWN + LEFT, buff=0.0)\n        self.panel_opener.set_x(panel_opener_x)\n        self.move_panel_and_controls_to_panel_opener()\n        return self\n\n    def close_panel(self):\n        panel_opener_x = self.panel_opener.get_x()\n        self.panel_opener.to_corner(UP + LEFT, buff=0.0)\n        self.panel_opener.set_x(panel_opener_x)\n        self.move_panel_and_controls_to_panel_opener()\n        return self\n\n    def panel_opener_on_mouse_drag(self, mob, event_data: dict[str, np.ndarray]) -> bool:\n        point = event_data[\"point\"]\n        self.panel_opener.match_y(Dot(point))\n        self.move_panel_and_controls_to_panel_opener()\n        return False\n\n    def panel_on_mouse_scroll(self, mob, event_data: dict[str, np.ndarray]) -> bool:\n        offset = event_data[\"offset\"]\n        factor = 10 * offset[1]\n        self.controls.set_y(self.controls.get_y() + factor)\n        return False\n"
  },
  {
    "path": "manimlib/mobject/matrix.py",
    "content": "from __future__ import annotations\n\nimport numpy as np\n\nfrom manimlib.constants import DOWN, LEFT, RIGHT, ORIGIN\nfrom manimlib.constants import DEG\nfrom manimlib.mobject.numbers import DecimalNumber\nfrom manimlib.mobject.svg.tex_mobject import Tex\nfrom manimlib.mobject.types.vectorized_mobject import VGroup\nfrom manimlib.mobject.types.vectorized_mobject import VMobject\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Sequence, Union, Optional\n    from manimlib.typing import ManimColor, Vect3, VectNArray, Self\n\n    StringMatrixType = Union[Sequence[Sequence[str]], np.ndarray[int, np.dtype[np.str_]]]\n    FloatMatrixType = Union[Sequence[Sequence[float]], VectNArray]\n    VMobjectMatrixType = Sequence[Sequence[VMobject]]\n    GenericMatrixType = Union[FloatMatrixType, StringMatrixType, VMobjectMatrixType]\n\n\nclass Matrix(VMobject):\n    def __init__(\n        self,\n        matrix: GenericMatrixType,\n        v_buff: float = 0.5,\n        h_buff: float = 0.5,\n        bracket_h_buff: float = 0.2,\n        bracket_v_buff: float = 0.25,\n        height: float | None = None,\n        element_config: dict = dict(),\n        element_alignment_corner: Vect3 = DOWN,\n        ellipses_row: Optional[int] = None,\n        ellipses_col: Optional[int] = None,\n    ):\n        \"\"\"\n        Matrix can either include numbers, tex_strings,\n        or mobjects\n        \"\"\"\n        super().__init__()\n\n        self.mob_matrix = self.create_mobject_matrix(\n            matrix, v_buff, h_buff, element_alignment_corner,\n            **element_config\n        )\n\n        # Create helpful groups for the elements\n        n_cols = len(self.mob_matrix[0])\n        self.elements = [elem for row in self.mob_matrix for elem in row]\n        self.columns = VGroup(*(\n            VGroup(*(row[i] for row in self.mob_matrix))\n            for i in range(n_cols)\n        ))\n        self.rows = VGroup(*(VGroup(*row) for row in self.mob_matrix))\n        if height is not None:\n            self.rows.set_height(height - 2 * bracket_v_buff)\n        self.brackets = self.create_brackets(self.rows, bracket_v_buff, bracket_h_buff)\n        self.ellipses = []\n\n        # Add elements and brackets\n        self.add(*self.elements)\n        self.add(*self.brackets)\n        self.center()\n\n        # Potentially add ellipses\n        self.swap_entries_for_ellipses(\n            ellipses_row,\n            ellipses_col,\n        )\n\n    def copy(self, deep: bool = False):\n        result = super().copy(deep)\n        self_family = self.get_family()\n        copy_family = result.get_family()\n        for attr in [\"elements\", \"ellipses\"]:\n            setattr(result, attr, [\n                copy_family[self_family.index(mob)]\n                for mob in getattr(self, attr)\n            ])\n        return result\n\n    def create_mobject_matrix(\n        self,\n        matrix: GenericMatrixType,\n        v_buff: float,\n        h_buff: float,\n        aligned_corner: Vect3,\n        **element_config\n    ) -> VMobjectMatrixType:\n        \"\"\"\n        Creates and organizes the matrix of mobjects\n        \"\"\"\n        mob_matrix = [\n            [\n                self.element_to_mobject(element, **element_config)\n                for element in row\n            ]\n            for row in matrix\n        ]\n        max_width = max(elem.get_width() for row in mob_matrix for elem in row)\n        max_height = max(elem.get_height() for row in mob_matrix for elem in row)\n        x_step = (max_width + h_buff) * RIGHT\n        y_step = (max_height + v_buff) * DOWN\n        for i, row in enumerate(mob_matrix):\n            for j, elem in enumerate(row):\n                elem.move_to(i * y_step + j * x_step, aligned_corner)\n        return mob_matrix\n\n    def element_to_mobject(self, element, **config) -> VMobject:\n        if isinstance(element, VMobject):\n            return element\n        elif isinstance(element, float | complex):\n            return DecimalNumber(element, **config)\n        else:\n            return Tex(str(element), **config)\n\n    def create_brackets(self, rows, v_buff: float, h_buff: float) -> VGroup:\n        brackets = Tex(\"\".join((\n            R\"\\left[\\begin{array}{c}\",\n            *len(rows) * [R\"\\quad \\\\\"],\n            R\"\\end{array}\\right]\",\n        )))\n        brackets.set_height(rows.get_height() + v_buff)\n        l_bracket = brackets[:len(brackets) // 2]\n        r_bracket = brackets[len(brackets) // 2:]\n        l_bracket.next_to(rows, LEFT, h_buff)\n        r_bracket.next_to(rows, RIGHT, h_buff)\n        return VGroup(l_bracket, r_bracket)\n\n    def get_column(self, index: int):\n        if not 0 <= index < len(self.columns):\n            raise IndexError(f\"Index {index} out of bound for matrix with {len(self.columns)} columns\")\n        return self.columns[index]\n\n    def get_row(self, index: int):\n        if not 0 <= index < len(self.rows):\n            raise IndexError(f\"Index {index} out of bound for matrix with {len(self.rows)} rows\")\n        return self.rows[index]\n\n    def get_columns(self) -> VGroup:\n        return self.columns\n\n    def get_rows(self) -> VGroup:\n        return self.rows\n\n    def set_column_colors(self, *colors: ManimColor) -> Self:\n        columns = self.get_columns()\n        for color, column in zip(colors, columns):\n            column.set_color(color)\n        return self\n\n    def add_background_to_entries(self) -> Self:\n        for mob in self.get_entries():\n            mob.add_background_rectangle()\n        return self\n\n    def swap_entry_for_dots(self, entry, dots):\n        dots.move_to(entry)\n        entry.become(dots)\n        if entry in self.elements:\n            self.elements.remove(entry)\n        if entry not in self.ellipses:\n            self.ellipses.append(entry)\n\n    def swap_entries_for_ellipses(\n        self,\n        row_index: Optional[int] = None,\n        col_index: Optional[int] = None,\n        height_ratio: float = 0.65,\n        width_ratio: float = 0.4\n    ):\n        rows = self.get_rows()\n        cols = self.get_columns()\n\n        avg_row_height = rows.get_height() / len(rows)\n        vdots_height = height_ratio * avg_row_height\n\n        avg_col_width = cols.get_width() / len(cols)\n        hdots_width = width_ratio * avg_col_width\n\n        use_vdots = row_index is not None and -len(rows) <= row_index < len(rows)\n        use_hdots = col_index is not None and -len(cols) <= col_index < len(cols)\n\n        if use_vdots:\n            for column in cols:\n                # Add vdots\n                dots = Tex(R\"\\vdots\")\n                dots.set_height(vdots_height)\n                self.swap_entry_for_dots(column[row_index], dots)\n        if use_hdots:\n            for row in rows:\n                # Add hdots\n                dots = Tex(R\"\\hdots\")\n                dots.set_width(hdots_width)\n                self.swap_entry_for_dots(row[col_index], dots)\n        if use_vdots and use_hdots:\n            rows[row_index][col_index].rotate(-45 * DEG)\n        return self\n\n    def get_mob_matrix(self) -> VMobjectMatrixType:\n        return self.mob_matrix\n\n    def get_entries(self) -> VGroup:\n        return VGroup(*self.elements)\n\n    def get_brackets(self) -> VGroup:\n        return VGroup(*self.brackets)\n\n    def get_ellipses(self) -> VGroup:\n        return VGroup(*self.ellipses)\n\n\nclass DecimalMatrix(Matrix):\n    def __init__(\n        self,\n        matrix: FloatMatrixType,\n        num_decimal_places: int = 2,\n        decimal_config: dict = dict(),\n        **config\n    ):\n        self.float_matrix = matrix\n        super().__init__(\n            matrix,\n            element_config=dict(\n                num_decimal_places=num_decimal_places,\n                **decimal_config\n            ),\n            **config\n        )\n\n    def element_to_mobject(self, element, **decimal_config) -> DecimalNumber:\n        return DecimalNumber(element, **decimal_config)\n\n\nclass IntegerMatrix(DecimalMatrix):\n    def __init__(\n        self,\n        matrix: FloatMatrixType,\n        num_decimal_places: int = 0,\n        decimal_config: dict = dict(),\n        **config\n    ):\n        super().__init__(matrix, num_decimal_places, decimal_config, **config)\n\n\nclass TexMatrix(Matrix):\n    def __init__(\n        self,\n        matrix: StringMatrixType,\n        tex_config: dict = dict(),\n        **config,\n    ):\n        super().__init__(\n            matrix,\n            element_config=tex_config,\n            **config\n        )\n\n\nclass MobjectMatrix(Matrix):\n    def __init__(\n        self,\n        group: VGroup,\n        n_rows: int | None = None,\n        n_cols: int | None = None,\n        height: float = 4.0,\n        element_alignment_corner=ORIGIN,\n        **config,\n    ):\n        # Have fallback defaults of n_rows and n_cols\n        n_mobs = len(group)\n        if n_rows is None:\n            n_rows = int(np.sqrt(n_mobs)) if n_cols is None else n_mobs // n_cols\n        if n_cols is None:\n            n_cols = n_mobs // n_rows\n\n        if len(group) < n_rows * n_cols:\n            raise Exception(\"Input to MobjectMatrix must have at least n_rows * n_cols entries\")\n\n        mob_matrix = [\n            [group[n * n_cols + k] for k in range(n_cols)]\n            for n in range(n_rows)\n        ]\n        config.update(\n            height=height,\n            element_alignment_corner=element_alignment_corner,\n        )\n        super().__init__(mob_matrix,  **config)\n\n    def element_to_mobject(self, element: VMobject, **config) -> VMobject:\n        return element\n"
  },
  {
    "path": "manimlib/mobject/mobject.py",
    "content": "from __future__ import annotations\n\nimport copy\nfrom functools import wraps\nimport itertools as it\nimport os\nimport pickle\nimport random\nimport sys\n\nimport moderngl\nimport numbers\nimport numpy as np\n\nfrom manimlib.constants import DEFAULT_MOBJECT_TO_EDGE_BUFF\nfrom manimlib.constants import DEFAULT_MOBJECT_TO_MOBJECT_BUFF\nfrom manimlib.constants import DOWN, IN, LEFT, ORIGIN, OUT, RIGHT, UP\nfrom manimlib.constants import FRAME_X_RADIUS, FRAME_Y_RADIUS\nfrom manimlib.constants import MED_SMALL_BUFF\nfrom manimlib.constants import TAU\nfrom manimlib.constants import DEFAULT_MOBJECT_COLOR\nfrom manimlib.event_handler import EVENT_DISPATCHER\nfrom manimlib.event_handler.event_listner import EventListener\nfrom manimlib.event_handler.event_type import EventType\nfrom manimlib.logger import log\nfrom manimlib.shader_wrapper import ShaderWrapper\nfrom manimlib.utils.color import color_gradient\nfrom manimlib.utils.color import color_to_rgb\nfrom manimlib.utils.color import get_colormap_list\nfrom manimlib.utils.color import rgb_to_hex\nfrom manimlib.utils.iterables import arrays_match\nfrom manimlib.utils.iterables import array_is_constant\nfrom manimlib.utils.iterables import batch_by_property\nfrom manimlib.utils.iterables import list_update\nfrom manimlib.utils.iterables import listify\nfrom manimlib.utils.iterables import resize_array\nfrom manimlib.utils.iterables import resize_preserving_order\nfrom manimlib.utils.iterables import resize_with_interpolation\nfrom manimlib.utils.bezier import integer_interpolate\nfrom manimlib.utils.bezier import interpolate\nfrom manimlib.utils.paths import straight_path\nfrom manimlib.utils.shaders import get_colormap_code\nfrom manimlib.utils.space_ops import angle_of_vector\nfrom manimlib.utils.space_ops import get_norm\nfrom manimlib.utils.space_ops import rotation_matrix_transpose\n\nfrom typing import TYPE_CHECKING\nfrom typing import TypeVar, Generic, Iterable\nSubmobjectType = TypeVar('SubmobjectType', bound='Mobject')\n\n\nif TYPE_CHECKING:\n    from typing import Callable, Iterator, Union, Tuple, Optional, Any\n    import numpy.typing as npt\n    from manimlib.typing import ManimColor, Vect3, Vect4Array, Vect3Array, UniformDict, Self\n    from moderngl.context import Context\n\n    T = TypeVar('T')\n    TimeBasedUpdater = Callable[[\"Mobject\", float], \"Mobject\" | None]\n    NonTimeUpdater = Callable[[\"Mobject\"], \"Mobject\" | None]\n    Updater = Union[TimeBasedUpdater, NonTimeUpdater]\n\n\nclass Mobject(object):\n    \"\"\"\n    Mathematical Object\n    \"\"\"\n    dim: int = 3\n    shader_folder: str = \"\"\n    render_primitive: int = moderngl.TRIANGLE_STRIP\n    # Must match in attributes of vert shader\n    data_dtype: np.dtype = np.dtype([\n        ('point', np.float32, (3,)),\n        ('rgba', np.float32, (4,)),\n    ])\n    aligned_data_keys = ['point']\n    pointlike_data_keys = ['point']\n\n    def __init__(\n        self,\n        color: ManimColor = DEFAULT_MOBJECT_COLOR,\n        opacity: float = 1.0,\n        shading: Tuple[float, float, float] = (0.0, 0.0, 0.0),\n        # For shaders\n        texture_paths: dict[str, str] | None = None,\n        # If true, the mobject will not get rotated according to camera position\n        is_fixed_in_frame: bool = False,\n        depth_test: bool = False,\n        z_index: int = 0,\n    ):\n        self.color = color\n        self.opacity = opacity\n        self.shading = shading\n        self.texture_paths = texture_paths\n        self.depth_test = depth_test\n        self.z_index = z_index\n\n        # Internal state\n        self.submobjects: list[Mobject] = []\n        self.parents: list[Mobject] = []\n        self.family: list[Mobject] | None = [self]\n        self.locked_data_keys: set[str] = set()\n        self.const_data_keys: set[str] = set()\n        self.locked_uniform_keys: set[str] = set()\n        self.saved_state = None\n        self.target = None\n        self.bounding_box: Vect3Array = np.zeros((3, 3))\n        self.shader_wrapper: Optional[ShaderWrapper] = None\n        self._is_animating: bool = False\n        self._needs_new_bounding_box: bool = True\n        self._data_has_changed: bool = True\n        self.shader_code_replacements: dict[str, str] = dict()\n\n        self.init_data()\n        self.init_uniforms()\n        self.init_updaters()\n        self.init_event_listners()\n        self.init_points()\n        self.init_colors()\n\n        if self.depth_test:\n            self.apply_depth_test()\n        if is_fixed_in_frame:\n            self.fix_in_frame()\n\n    def __str__(self):\n        return self.__class__.__name__\n\n    def __add__(self, other: Mobject) -> Mobject:\n        assert isinstance(other, Mobject)\n        return self.get_group_class()(self, other)\n\n    def __mul__(self, other: int) -> Mobject:\n        assert isinstance(other, int)\n        return self.replicate(other)\n\n    def init_data(self, length: int = 0):\n        self.data = np.zeros(length, dtype=self.data_dtype)\n        self._data_defaults = np.ones(1, dtype=self.data.dtype)\n\n    def init_uniforms(self):\n        self.uniforms: UniformDict = {\n            \"is_fixed_in_frame\": 0.0,\n            \"shading\": np.array(self.shading, dtype=float),\n            \"clip_plane0\": np.zeros(4),\n            \"clip_plane1\": np.zeros(4),\n            \"clip_plane2\": np.zeros(4),\n            \"clip_plane3\": np.zeros(4),\n        }\n\n    def init_colors(self):\n        self.set_color(self.color, self.opacity)\n\n    def init_points(self):\n        # Typically implemented in subclass, unlpess purposefully left blank\n        pass\n\n    def set_uniforms(self, uniforms: dict) -> Self:\n        for key, value in uniforms.items():\n            if isinstance(value, np.ndarray):\n                value = value.copy()\n            self.uniforms[key] = value\n        return self\n\n    @property\n    def animate(self) -> _AnimationBuilder | Self:\n        \"\"\"\n        Methods called with Mobject.animate.method() can be passed\n        into a Scene.play call, as if you were calling\n        ApplyMethod(mobject.method)\n\n        Borrowed from https://github.com/ManimCommunity/manim/\n        \"\"\"\n        return _AnimationBuilder(self)\n\n    @property\n    def always(self) -> _UpdaterBuilder:\n        \"\"\"\n        Methods called with mobject.always.method(*args, **kwargs)\n        will result in the call mobject.method(*args, **kwargs)\n        on every frame\n        \"\"\"\n        return _UpdaterBuilder(self)\n\n    @property\n    def f_always(self) -> _FunctionalUpdaterBuilder:\n        \"\"\"\n        Similar to Mobject.always, but with the intent that arguments\n        are functions returning the corresponding type fit for the method\n        Methods called with\n        mobject.f_always.method(\n            func1, func2, ...,\n            kwarg1=kw_func1,\n            kwarg2=kw_func2,\n            ...\n        )\n        will result in the call\n        mobject.method(\n            func1(), func2(), ...,\n            kwarg1=kw_func1(),\n            kwarg2=kw_func2(),\n            ...\n        )\n        on every frame\n        \"\"\"\n        return _FunctionalUpdaterBuilder(self)\n\n    def note_changed_data(self, recurse_up: bool = True) -> Self:\n        self._data_has_changed = True\n        if recurse_up:\n            for mob in self.parents:\n                mob.note_changed_data()\n        return self\n\n    @staticmethod\n    def affects_data(func: Callable[..., T]) -> Callable[..., T]:\n        @wraps(func)\n        def wrapper(self, *args, **kwargs):\n            result = func(self, *args, **kwargs)\n            self.note_changed_data()\n            return result\n        return wrapper\n\n    @staticmethod\n    def affects_family_data(func: Callable[..., T]) -> Callable[..., T]:\n        @wraps(func)\n        def wrapper(self, *args, **kwargs):\n            result = func(self, *args, **kwargs)\n            for mob in self.family_members_with_points():\n                mob.note_changed_data()\n            return result\n        return wrapper\n\n    # Only these methods should directly affect points\n    @affects_data\n    def set_data(self, data: np.ndarray) -> Self:\n        assert data.dtype == self.data.dtype\n        self.resize_points(len(data))\n        self.data[:] = data\n        return self\n\n    @affects_data\n    def resize_points(\n        self,\n        new_length: int,\n        resize_func: Callable[[np.ndarray, int], np.ndarray] = resize_array\n    ) -> Self:\n        if new_length == 0:\n            if len(self.data) > 0:\n                self._data_defaults[:1] = self.data[:1]\n        elif self.get_num_points() == 0:\n            self.data = self._data_defaults.copy()\n\n        self.data = resize_func(self.data, new_length)\n        self.refresh_bounding_box()\n        return self\n\n    @affects_data\n    def set_points(self, points: Vect3Array | list[Vect3]) -> Self:\n        self.resize_points(len(points), resize_func=resize_preserving_order)\n        self.data[\"point\"][:] = points\n        return self\n\n    @affects_data\n    def append_points(self, new_points: Vect3Array) -> Self:\n        n = self.get_num_points()\n        self.resize_points(n + len(new_points))\n        # Have most data default to the last value\n        self.data[n:] = self.data[n - 1]\n        # Then read in new points\n        self.data[\"point\"][n:] = new_points\n        self.refresh_bounding_box()\n        return self\n\n    @affects_family_data\n    def reverse_points(self) -> Self:\n        for mob in self.get_family():\n            mob.data[:] = mob.data[::-1]\n        return self\n\n    @affects_family_data\n    def apply_points_function(\n        self,\n        func: Callable[[np.ndarray], np.ndarray],\n        about_point: Vect3 | None = None,\n        about_edge: Vect3 = ORIGIN,\n        works_on_bounding_box: bool = False\n    ) -> Self:\n        if about_point is None and about_edge is not None:\n            about_point = self.get_bounding_box_point(about_edge)\n\n        for mob in self.get_family():\n            arrs = [mob.data[key] for key in mob.pointlike_data_keys if mob.has_points()]\n            if works_on_bounding_box:\n                arrs.append(mob.get_bounding_box())\n\n            for arr in arrs:\n                if about_point is None:\n                    arr[:] = func(arr)\n                else:\n                    arr[:] = func(arr - about_point) + about_point\n\n        if not works_on_bounding_box:\n            self.refresh_bounding_box(recurse_down=True)\n        else:\n            for parent in self.parents:\n                parent.refresh_bounding_box()\n        return self\n\n    @affects_data\n    def match_points(self, mobject: Mobject) -> Self:\n        self.resize_points(len(mobject.data), resize_func=resize_preserving_order)\n        for key in self.pointlike_data_keys:\n            self.data[key][:] = mobject.data[key]\n        return self\n\n    # Others related to points\n\n    def get_points(self) -> Vect3Array:\n        return self.data[\"point\"]\n\n    def clear_points(self) -> Self:\n        self.resize_points(0)\n        return self\n\n    def get_num_points(self) -> int:\n        return len(self.get_points())\n\n    def get_all_points(self) -> Vect3Array:\n        if self.submobjects:\n            return np.vstack([sm.get_points() for sm in self.get_family()])\n        else:\n            return self.get_points()\n\n    def has_points(self) -> bool:\n        return len(self.get_points()) > 0\n\n    def get_bounding_box(self) -> Vect3Array:\n        if self._needs_new_bounding_box:\n            self.bounding_box[:] = self.compute_bounding_box()\n            self._needs_new_bounding_box = False\n        return self.bounding_box\n\n    def compute_bounding_box(self) -> Vect3Array:\n        all_points = np.vstack([\n            self.get_points(),\n            *(\n                mob.get_bounding_box()\n                for mob in self.get_family()[1:]\n                if mob.has_points()\n            )\n        ])\n        if len(all_points) == 0:\n            return np.zeros((3, self.dim))\n        else:\n            # Lower left and upper right corners\n            mins = all_points.min(0)\n            maxs = all_points.max(0)\n            mids = (mins + maxs) / 2\n            return np.array([mins, mids, maxs])\n\n    def refresh_bounding_box(\n        self,\n        recurse_down: bool = False,\n        recurse_up: bool = True\n    ) -> Self:\n        for mob in self.get_family(recurse_down):\n            mob._needs_new_bounding_box = True\n        if recurse_up:\n            for parent in self.parents:\n                parent.refresh_bounding_box()\n        return self\n\n    def are_points_touching(\n        self,\n        points: Vect3Array,\n        buff: float = 0\n    ) -> np.ndarray:\n        bb = self.get_bounding_box()\n        mins = (bb[0] - buff)\n        maxs = (bb[2] + buff)\n        return ((points >= mins) * (points <= maxs)).all(1)\n\n    def is_point_touching(\n        self,\n        point: Vect3,\n        buff: float = 0\n    ) -> bool:\n        return self.are_points_touching(np.array(point, ndmin=2), buff)[0]\n\n    def is_touching(self, mobject: Mobject, buff: float = 1e-2) -> bool:\n        bb1 = self.get_bounding_box()\n        bb2 = mobject.get_bounding_box()\n        return not any((\n            (bb2[2] < bb1[0] - buff).any(),  # E.g. Right of mobject is left of self's left\n            (bb2[0] > bb1[2] + buff).any(),  # E.g. Left of mobject is right of self's right\n        ))\n\n    # Family matters\n\n    def __getitem__(self, value: int | slice) -> Mobject:\n        if isinstance(value, slice):\n            GroupClass = self.get_group_class()\n            return GroupClass(*self.split().__getitem__(value))\n        return self.split().__getitem__(value)\n\n    def __iter__(self) -> Iterator[Self]:\n        return iter(self.split())\n\n    def __len__(self) -> int:\n        return len(self.split())\n\n    def split(self) -> list[Self]:\n        return self.submobjects\n\n    @affects_data\n    def note_changed_family(self, only_changed_order=False) -> Self:\n        self.family = None\n        if not only_changed_order:\n            self.refresh_has_updater_status()\n            self.refresh_bounding_box()\n        for parent in self.parents:\n            parent.note_changed_family()\n        return self\n\n    def get_family(self, recurse: bool = True) -> list[Mobject]:\n        if not recurse:\n            return [self]\n        if self.family is None:\n            # Reconstruct and save\n            sub_families = (sm.get_family() for sm in self.submobjects)\n            self.family = [self, *it.chain(*sub_families)]\n        return self.family\n\n    def family_members_with_points(self) -> list[Mobject]:\n        return [m for m in self.get_family() if len(m.data) > 0]\n\n    def get_ancestors(self, extended: bool = False) -> list[Mobject]:\n        \"\"\"\n        Returns parents, grandparents, etc.\n        Order of result should be from higher members of the hierarchy down.\n\n        If extended is set to true, it includes the ancestors of all family members,\n        e.g. any other parents of a submobject\n        \"\"\"\n        ancestors = []\n        to_process = list(self.get_family(recurse=extended))\n        excluded = set(to_process)\n        while to_process:\n            for p in to_process.pop().parents:\n                if p not in excluded:\n                    ancestors.append(p)\n                    to_process.append(p)\n        # Ensure mobjects highest in the hierarchy show up first\n        ancestors.reverse()\n        # Remove list redundancies while preserving order\n        return list(dict.fromkeys(ancestors))\n\n    def add(self, *mobjects: Mobject) -> Self:\n        if self in mobjects:\n            raise Exception(\"Mobject cannot contain self\")\n        for mobject in mobjects:\n            if mobject not in self.submobjects:\n                self.submobjects.append(mobject)\n            if self not in mobject.parents:\n                mobject.parents.append(self)\n        self.note_changed_family()\n        return self\n\n    def remove(\n        self,\n        *to_remove: Mobject,\n        reassemble: bool = True,\n        recurse: bool = True\n    ) -> Self:\n        for parent in self.get_family(recurse):\n            for child in to_remove:\n                if child in parent.submobjects:\n                    parent.submobjects.remove(child)\n                if parent in child.parents:\n                    child.parents.remove(parent)\n            if reassemble:\n                parent.note_changed_family()\n        return self\n\n    def clear(self) -> Self:\n        self.remove(*self.submobjects, recurse=False)\n        return self\n\n    def add_to_back(self, *mobjects: Mobject) -> Self:\n        self.set_submobjects(list_update(mobjects, self.submobjects))\n        return self\n\n    def replace_submobject(self, index: int, new_submob: Mobject) -> Self:\n        old_submob = self.submobjects[index]\n        if self in old_submob.parents:\n            old_submob.parents.remove(self)\n        self.submobjects[index] = new_submob\n        new_submob.parents.append(self)\n        self.note_changed_family()\n        return self\n\n    def insert_submobject(self, index: int, new_submob: Mobject) -> Self:\n        self.submobjects.insert(index, new_submob)\n        self.note_changed_family()\n        return self\n\n    def set_submobjects(self, submobject_list: list[Mobject]) -> Self:\n        if self.submobjects == submobject_list:\n            return self\n        self.clear()\n        self.add(*submobject_list)\n        return self\n\n    def digest_mobject_attrs(self) -> Self:\n        \"\"\"\n        Ensures all attributes which are mobjects are included\n        in the submobjects list.\n        \"\"\"\n        mobject_attrs = [x for x in list(self.__dict__.values()) if isinstance(x, Mobject)]\n        self.set_submobjects(list_update(self.submobjects, mobject_attrs))\n        return self\n\n    # Submobject organization\n\n    def arrange(\n        self,\n        direction: Vect3 = RIGHT,\n        center: bool = True,\n        **kwargs\n    ) -> Self:\n        for m1, m2 in zip(self.submobjects, self.submobjects[1:]):\n            m2.next_to(m1, direction, **kwargs)\n        if center:\n            self.center()\n        return self\n\n    def arrange_in_grid(\n        self,\n        n_rows: int | None = None,\n        n_cols: int | None = None,\n        buff: float | None = None,\n        h_buff: float | None = None,\n        v_buff: float | None = None,\n        buff_ratio: float | None = None,\n        h_buff_ratio: float = 0.5,\n        v_buff_ratio: float = 0.5,\n        aligned_edge: Vect3 = ORIGIN,\n        fill_rows_first: bool = True\n    ) -> Self:\n        submobs = self.submobjects\n        n_submobs = len(submobs)\n        if n_rows is None:\n            n_rows = int(np.sqrt(n_submobs)) if n_cols is None else n_submobs // n_cols\n        if n_cols is None:\n            n_cols = n_submobs // n_rows\n\n        if buff is not None:\n            h_buff = buff\n            v_buff = buff\n        else:\n            if buff_ratio is not None:\n                v_buff_ratio = buff_ratio\n                h_buff_ratio = buff_ratio\n            if h_buff is None:\n                h_buff = h_buff_ratio * self[0].get_width()\n            if v_buff is None:\n                v_buff = v_buff_ratio * self[0].get_height()\n\n        x_unit = h_buff + max([sm.get_width() for sm in submobs])\n        y_unit = v_buff + max([sm.get_height() for sm in submobs])\n\n        for index, sm in enumerate(submobs):\n            if fill_rows_first:\n                x, y = index % n_cols, index // n_cols\n            else:\n                x, y = index // n_rows, index % n_rows\n            sm.move_to(ORIGIN, aligned_edge)\n            sm.shift(x * x_unit * RIGHT + y * y_unit * DOWN)\n        self.center()\n        return self\n\n    def arrange_to_fit_dim(self, length: float, dim: int, about_edge=ORIGIN) -> Self:\n        ref_point = self.get_bounding_box_point(about_edge)\n        n_submobs = len(self.submobjects)\n        if n_submobs <= 1:\n            return\n        total_length = sum(sm.length_over_dim(dim) for sm in self.submobjects)\n        buff = (length - total_length) / (n_submobs - 1)\n        vect = np.zeros(self.dim)\n        vect[dim] = 1\n        x = 0\n        for submob in self.submobjects:\n            submob.set_coord(x, dim, -vect)\n            x += submob.length_over_dim(dim) + buff\n        self.move_to(ref_point, about_edge)\n        return self\n\n    def arrange_to_fit_width(self, width: float, about_edge=ORIGIN) -> Self:\n        return self.arrange_to_fit_dim(width, 0, about_edge)\n\n    def arrange_to_fit_height(self, height: float, about_edge=ORIGIN) -> Self:\n        return self.arrange_to_fit_dim(height, 1, about_edge)\n\n    def arrange_to_fit_depth(self, depth: float, about_edge=ORIGIN) -> Self:\n        return self.arrange_to_fit_dim(depth, 2, about_edge)\n\n    def sort(\n        self,\n        point_to_num_func: Callable[[np.ndarray], float] = lambda p: p[0],\n        submob_func: Callable[[Mobject]] | None = None\n    ) -> Self:\n        if submob_func is not None:\n            self.submobjects.sort(key=submob_func)\n        else:\n            self.submobjects.sort(key=lambda m: point_to_num_func(m.get_center()))\n        self.note_changed_family(only_changed_order=True)\n        return self\n\n    def shuffle(self, recurse: bool = False) -> Self:\n        if recurse:\n            for submob in self.submobjects:\n                submob.shuffle(recurse=True)\n        random.shuffle(self.submobjects)\n        self.note_changed_family(only_changed_order=True)\n        return self\n\n    def reverse_submobjects(self) -> Self:\n        self.submobjects.reverse()\n        self.note_changed_family(only_changed_order=True)\n        return self\n\n    # Copying and serialization\n\n    @staticmethod\n    def stash_mobject_pointers(func: Callable[..., T]) -> Callable[..., T]:\n        @wraps(func)\n        def wrapper(self, *args, **kwargs):\n            uncopied_attrs = [\"parents\", \"target\", \"saved_state\"]\n            stash = dict()\n            for attr in uncopied_attrs:\n                if hasattr(self, attr):\n                    value = getattr(self, attr)\n                    stash[attr] = value\n                    null_value = [] if isinstance(value, list) else None\n                    setattr(self, attr, null_value)\n            result = func(self, *args, **kwargs)\n            self.__dict__.update(stash)\n            return result\n        return wrapper\n\n    @stash_mobject_pointers\n    def serialize(self) -> bytes:\n        return pickle.dumps(self)\n\n    def deserialize(self, data: bytes) -> Self:\n        self.become(pickle.loads(data))\n        return self\n\n    @stash_mobject_pointers\n    def deepcopy(self) -> Self:\n        return copy.deepcopy(self)\n\n    def copy(self, deep: bool = False) -> Self:\n        if deep:\n            return self.deepcopy()\n\n        result = copy.copy(self)\n\n        result.parents = []\n        result.target = None\n        result.saved_state = None\n\n        # copy.copy is only a shallow copy, so the internal\n        # data which are numpy arrays or other mobjects still\n        # need to be further copied.\n        result.uniforms = {\n            key: value.copy() if isinstance(value, np.ndarray) else value\n            for key, value in self.uniforms.items()\n        }\n\n        # Instead of adding using result.add, which does some checks for updating\n        # updater statues and bounding box, just directly modify the family-related\n        # lists\n        result.submobjects = [sm.copy() for sm in self.submobjects]\n        for sm in result.submobjects:\n            sm.parents = [result]\n        result.family = [result, *it.chain(*(sm.get_family() for sm in result.submobjects))]\n\n        # Similarly, instead of calling match_updaters, since we know the status\n        # won't have changed, just directly match.\n        result.updaters = list(self.updaters)\n        result._data_has_changed = True\n        result.shader_wrapper = None\n\n        family = self.get_family()\n        for attr, value in self.__dict__.items():\n            if isinstance(value, Mobject) and value is not self:\n                if value in family:\n                    setattr(result, attr, result.family[family.index(value)])\n            elif isinstance(value, np.ndarray):\n                setattr(result, attr, value.copy())\n        return result\n\n    def generate_target(self, use_deepcopy: bool = False) -> Self:\n        self.target = self.copy(deep=use_deepcopy)\n        self.target.saved_state = self.saved_state\n        return self.target\n\n    def save_state(self, use_deepcopy: bool = False) -> Self:\n        self.saved_state = self.copy(deep=use_deepcopy)\n        self.saved_state.target = self.target\n        return self\n\n    def restore(self) -> Self:\n        if not hasattr(self, \"saved_state\") or self.saved_state is None:\n            raise Exception(\"Trying to restore without having saved\")\n        self.become(self.saved_state)\n        return self\n\n    def become(self, mobject: Mobject, match_updaters=False) -> Self:\n        \"\"\"\n        Edit all data and submobjects to be idential\n        to another mobject\n        \"\"\"\n        self.align_family(mobject)\n        family1 = self.get_family()\n        family2 = mobject.get_family()\n        for sm1, sm2 in zip(family1, family2):\n            sm1.set_data(sm2.data)\n            sm1.set_uniforms(sm2.uniforms)\n            sm1.bounding_box[:] = sm2.bounding_box\n            sm1.shader_folder = sm2.shader_folder\n            sm1.texture_paths = sm2.texture_paths\n            sm1.depth_test = sm2.depth_test\n            sm1.render_primitive = sm2.render_primitive\n            sm1._needs_new_bounding_box = sm2._needs_new_bounding_box\n        # Make sure named family members carry over\n        for attr, value in list(mobject.__dict__.items()):\n            if isinstance(value, Mobject) and value in family2:\n                setattr(self, attr, family1[family2.index(value)])\n        if match_updaters:\n            self.match_updaters(mobject)\n        return self\n\n    def looks_identical(self, mobject: Mobject) -> bool:\n        fam1 = self.family_members_with_points()\n        fam2 = mobject.family_members_with_points()\n        if len(fam1) != len(fam2):\n            return False\n        for m1, m2 in zip(fam1, fam2):\n            if m1.get_num_points() != m2.get_num_points():\n                return False\n            if not m1.data.dtype == m2.data.dtype:\n                return False\n            for key in m1.data.dtype.names:\n                if not np.isclose(m1.data[key], m2.data[key]).all():\n                    return False\n            if set(m1.uniforms).difference(m2.uniforms):\n                return False\n            for key in m1.uniforms:\n                value1 = m1.uniforms[key]\n                value2 = m2.uniforms[key]\n                if isinstance(value1, np.ndarray) and isinstance(value2, np.ndarray) and not value1.size == value2.size:\n                    return False\n                if not np.isclose(value1, value2).all():\n                    return False\n        return True\n\n    def has_same_shape_as(self, mobject: Mobject) -> bool:\n        # Normalize both point sets by centering and making height 1\n        points1, points2 = (\n            (m.get_all_points() - m.get_center()) / m.get_height()\n            for m in (self, mobject)\n        )\n        if len(points1) != len(points2):\n            return False\n        return bool(np.isclose(points1, points2, atol=self.get_width() * 1e-2).all())\n\n    # Creating new Mobjects from this one\n\n    def replicate(self, n: int) -> Self:\n        group_class = self.get_group_class()\n        return group_class(*(self.copy() for _ in range(n)))\n\n    def get_grid(\n        self,\n        n_rows: int,\n        n_cols: int,\n        height: float | None = None,\n        width: float | None = None,\n        group_by_rows: bool = False,\n        group_by_cols: bool = False,\n        **kwargs\n    ) -> Self:\n        \"\"\"\n        Returns a new mobject containing multiple copies of this one\n        arranged in a grid\n        \"\"\"\n        total = n_rows * n_cols\n        grid = self.replicate(total)\n        if group_by_cols:\n            kwargs[\"fill_rows_first\"] = False\n        grid.arrange_in_grid(n_rows, n_cols, **kwargs)\n        if height is not None:\n            grid.set_height(height)\n        if width is not None:\n            grid.set_height(width)\n\n        group_class = self.get_group_class()\n        if group_by_rows:\n            return group_class(*(grid[n:n + n_cols] for n in range(0, total, n_cols)))\n        elif group_by_cols:\n            return group_class(*(grid[n:n + n_rows] for n in range(0, total, n_rows)))\n        else:\n            return grid\n\n    # Updating\n\n    def init_updaters(self):\n        self.updaters: list[Updater] = list()\n        self._has_updaters_in_family: Optional[bool] = False\n        self.updating_suspended: bool = False\n\n    def update(self, dt: float = 0, recurse: bool = True) -> Self:\n        if not self.has_updaters() or self.updating_suspended:\n            return self\n        if recurse:\n            for submob in self.submobjects:\n                submob.update(dt, recurse)\n        for updater in self.updaters:\n            # This is hacky, but if an updater takes dt as an arg,\n            # it will be passed the change in time from here\n            if \"dt\" in updater.__code__.co_varnames:\n                updater(self, dt=dt)\n            else:\n                updater(self)\n        return self\n\n    def get_updaters(self) -> list[Updater]:\n        return self.updaters\n\n    def add_updater(self, update_func: Updater, call: bool = True) -> Self:\n        self.updaters.append(update_func)\n        if call:\n            self.update(dt=0)\n        self.refresh_has_updater_status()\n        self.update()\n        return self\n\n    def insert_updater(self, update_func: Updater, index=0):\n        self.updaters.insert(index, update_func)\n        self.refresh_has_updater_status()\n        return self\n\n    def remove_updater(self, update_func: Updater) -> Self:\n        while update_func in self.updaters:\n            self.updaters.remove(update_func)\n        self.refresh_has_updater_status()\n        return self\n\n    def clear_updaters(self, recurse: bool = True) -> Self:\n        for mob in self.get_family(recurse):\n            mob.updaters = []\n            mob._has_updaters_in_family = False\n        for parent in self.get_ancestors():\n            parent._has_updaters_in_family = False\n        return self\n\n    def match_updaters(self, mobject: Mobject) -> Self:\n        self.updaters = list(mobject.updaters)\n        self.refresh_has_updater_status()\n        return self\n\n    def suspend_updating(self, recurse: bool = True) -> Self:\n        self.updating_suspended = True\n        if recurse:\n            for submob in self.submobjects:\n                submob.suspend_updating(recurse)\n        return self\n\n    def resume_updating(self, recurse: bool = True, call_updater: bool = True) -> Self:\n        self.updating_suspended = False\n        if recurse:\n            for submob in self.submobjects:\n                submob.resume_updating(recurse)\n        for parent in self.parents:\n            parent.resume_updating(recurse=False, call_updater=False)\n        if call_updater:\n            self.update(dt=0, recurse=recurse)\n        return self\n\n    def has_updaters(self) -> bool:\n        if self._has_updaters_in_family is None:\n            # Recompute and save\n            self._has_updaters_in_family = bool(self.updaters) or any(\n                sm.has_updaters() for sm in self.submobjects\n            )\n        return self._has_updaters_in_family\n\n    def refresh_has_updater_status(self) -> Self:\n        self._has_updaters_in_family = None\n        for parent in self.parents:\n            parent.refresh_has_updater_status()\n        return self\n\n    # Check if mark as static or not for camera\n\n    def is_changing(self) -> bool:\n        return self._is_animating or self.has_updaters()\n\n    def set_animating_status(self, is_animating: bool, recurse: bool = True) -> Self:\n        for mob in (*self.get_family(recurse), *self.get_ancestors()):\n            mob._is_animating = is_animating\n        return self\n\n    # Transforming operations\n\n    def shift(self, vector: Vect3) -> Self:\n        self.apply_points_function(\n            lambda points: points + vector,\n            about_edge=None,\n            works_on_bounding_box=True,\n        )\n        return self\n\n    def scale(\n        self,\n        scale_factor: float | npt.ArrayLike,\n        min_scale_factor: float = 1e-8,\n        about_point: Vect3 | None = None,\n        about_edge: Vect3 = ORIGIN\n    ) -> Self:\n        \"\"\"\n        Default behavior is to scale about the center of the mobject.\n        The argument about_edge can be a vector, indicating which side of\n        the mobject to scale about, e.g., mob.scale(about_edge = RIGHT)\n        scales about mob.get_right().\n\n        Otherwise, if about_point is given a value, scaling is done with\n        respect to that point.\n        \"\"\"\n        if isinstance(scale_factor, numbers.Number):\n            scale_factor = max(scale_factor, min_scale_factor)\n        else:\n            scale_factor = np.array(scale_factor).clip(min=min_scale_factor)\n        self.apply_points_function(\n            lambda points: scale_factor * points,\n            about_point=about_point,\n            about_edge=about_edge,\n            works_on_bounding_box=True,\n        )\n        for mob in self.get_family():\n            mob._handle_scale_side_effects(scale_factor)\n        return self\n\n    def _handle_scale_side_effects(self, scale_factor):\n        # In case subclasses, such as DecimalNumber, need to make\n        # any other changes when the size gets altered\n        pass\n\n    def stretch(self, factor: float, dim: int, **kwargs) -> Self:\n        def func(points):\n            points[:, dim] *= factor\n            return points\n        self.apply_points_function(func, works_on_bounding_box=True, **kwargs)\n        return self\n\n    def rotate_about_origin(self, angle: float, axis: Vect3 = OUT) -> Self:\n        return self.rotate(angle, axis, about_point=ORIGIN)\n\n    def rotate(\n        self,\n        angle: float,\n        axis: Vect3 = OUT,\n        about_point: Vect3 | None = None,\n        **kwargs\n    ) -> Self:\n        rot_matrix_T = rotation_matrix_transpose(angle, axis)\n        self.apply_points_function(\n            lambda points: np.dot(points, rot_matrix_T),\n            about_point,\n            **kwargs\n        )\n        return self\n\n    def flip(self, axis: Vect3 = UP, **kwargs) -> Self:\n        return self.rotate(TAU / 2, axis, **kwargs)\n\n    def apply_function(self, function: Callable[[np.ndarray], np.ndarray], **kwargs) -> Self:\n        # Default to applying matrix about the origin, not mobjects center\n        if len(kwargs) == 0:\n            kwargs[\"about_point\"] = ORIGIN\n        self.apply_points_function(\n            lambda points: np.array([function(p) for p in points]),\n            **kwargs\n        )\n        return self\n\n    def apply_function_to_position(self, function: Callable[[np.ndarray], np.ndarray]) -> Self:\n        self.move_to(function(self.get_center()))\n        return self\n\n    def apply_function_to_submobject_positions(\n        self,\n        function: Callable[[np.ndarray], np.ndarray]\n    ) -> Self:\n        for submob in self.submobjects:\n            submob.apply_function_to_position(function)\n        return self\n\n    def apply_matrix(self, matrix: npt.ArrayLike, **kwargs) -> Self:\n        # Default to applying matrix about the origin, not mobjects center\n        if (\"about_point\" not in kwargs) and (\"about_edge\" not in kwargs):\n            kwargs[\"about_point\"] = ORIGIN\n        full_matrix = np.identity(self.dim)\n        matrix = np.array(matrix)\n        full_matrix[:matrix.shape[0], :matrix.shape[1]] = matrix\n        self.apply_points_function(\n            lambda points: np.dot(points, full_matrix.T),\n            **kwargs\n        )\n        return self\n\n    def apply_complex_function(self, function: Callable[[complex], complex], **kwargs) -> Self:\n        def R3_func(point):\n            x, y, z = point\n            xy_complex = function(complex(x, y))\n            return [\n                xy_complex.real,\n                xy_complex.imag,\n                z\n            ]\n        return self.apply_function(R3_func, **kwargs)\n\n    def wag(\n        self,\n        direction: Vect3 = RIGHT,\n        axis: Vect3 = DOWN,\n        wag_factor: float = 1.0\n    ) -> Self:\n        for mob in self.family_members_with_points():\n            alphas = np.dot(mob.get_points(), np.transpose(axis))\n            alphas -= min(alphas)\n            alphas /= max(alphas)\n            alphas = alphas**wag_factor\n            mob.set_points(mob.get_points() + np.dot(\n                alphas.reshape((len(alphas), 1)),\n                np.array(direction).reshape((1, mob.dim))\n            ))\n        return self\n\n    # Positioning methods\n\n    def center(self) -> Self:\n        self.shift(-self.get_center())\n        return self\n\n    def align_on_border(\n        self,\n        direction: Vect3,\n        buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFF\n    ) -> Self:\n        \"\"\"\n        Direction just needs to be a vector pointing towards side or\n        corner in the 2d plane.\n        \"\"\"\n        target_point = np.sign(direction) * (FRAME_X_RADIUS, FRAME_Y_RADIUS, 0)\n        point_to_align = self.get_bounding_box_point(direction)\n        shift_val = target_point - point_to_align - buff * np.array(direction)\n        shift_val = shift_val * abs(np.sign(direction))\n        self.shift(shift_val)\n        return self\n\n    def to_corner(\n        self,\n        corner: Vect3 = LEFT + DOWN,\n        buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFF\n    ) -> Self:\n        return self.align_on_border(corner, buff)\n\n    def to_edge(\n        self,\n        edge: Vect3 = LEFT,\n        buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFF\n    ) -> Self:\n        return self.align_on_border(edge, buff)\n\n    def next_to(\n        self,\n        mobject_or_point: Mobject | Vect3,\n        direction: Vect3 = RIGHT,\n        buff: float = DEFAULT_MOBJECT_TO_MOBJECT_BUFF,\n        aligned_edge: Vect3 = ORIGIN,\n        submobject_to_align: Mobject | None = None,\n        index_of_submobject_to_align: int | slice | None = None,\n        coor_mask: Vect3 = np.array([1, 1, 1]),\n    ) -> Self:\n        if isinstance(mobject_or_point, Mobject):\n            mob = mobject_or_point\n            if index_of_submobject_to_align is not None:\n                target_aligner = mob[index_of_submobject_to_align]\n            else:\n                target_aligner = mob\n            target_point = target_aligner.get_bounding_box_point(\n                aligned_edge + direction\n            )\n        else:\n            target_point = mobject_or_point\n        if submobject_to_align is not None:\n            aligner = submobject_to_align\n        elif index_of_submobject_to_align is not None:\n            aligner = self[index_of_submobject_to_align]\n        else:\n            aligner = self\n        point_to_align = aligner.get_bounding_box_point(aligned_edge - direction)\n        self.shift((target_point - point_to_align + buff * direction) * coor_mask)\n        return self\n\n    def shift_onto_screen(self, **kwargs) -> Self:\n        space_lengths = [FRAME_X_RADIUS, FRAME_Y_RADIUS]\n        for vect in UP, DOWN, LEFT, RIGHT:\n            dim = np.argmax(np.abs(vect))\n            buff = kwargs.get(\"buff\", DEFAULT_MOBJECT_TO_EDGE_BUFF)\n            max_val = space_lengths[dim] - buff\n            edge_center = self.get_edge_center(vect)\n            if np.dot(edge_center, vect) > max_val:\n                self.to_edge(vect, **kwargs)\n        return self\n\n    def is_off_screen(self) -> bool:\n        if self.get_left()[0] > FRAME_X_RADIUS:\n            return True\n        if self.get_right()[0] < -FRAME_X_RADIUS:\n            return True\n        if self.get_bottom()[1] > FRAME_Y_RADIUS:\n            return True\n        if self.get_top()[1] < -FRAME_Y_RADIUS:\n            return True\n        return False\n\n    def stretch_about_point(self, factor: float, dim: int, point: Vect3) -> Self:\n        return self.stretch(factor, dim, about_point=point)\n\n    def stretch_in_place(self, factor: float, dim: int) -> Self:\n        # Now redundant with stretch\n        return self.stretch(factor, dim)\n\n    def rescale_to_fit(self, length: float, dim: int, stretch: bool = False, **kwargs) -> Self:\n        old_length = self.length_over_dim(dim)\n        if old_length == 0:\n            return self\n        if stretch:\n            self.stretch(length / old_length, dim, **kwargs)\n        else:\n            self.scale(length / old_length, **kwargs)\n        return self\n\n    def stretch_to_fit_width(self, width: float, **kwargs) -> Self:\n        return self.rescale_to_fit(width, 0, stretch=True, **kwargs)\n\n    def stretch_to_fit_height(self, height: float, **kwargs) -> Self:\n        return self.rescale_to_fit(height, 1, stretch=True, **kwargs)\n\n    def stretch_to_fit_depth(self, depth: float, **kwargs) -> Self:\n        return self.rescale_to_fit(depth, 2, stretch=True, **kwargs)\n\n    def set_width(self, width: float, stretch: bool = False, **kwargs) -> Self:\n        return self.rescale_to_fit(width, 0, stretch=stretch, **kwargs)\n\n    def set_height(self, height: float, stretch: bool = False, **kwargs) -> Self:\n        return self.rescale_to_fit(height, 1, stretch=stretch, **kwargs)\n\n    def set_depth(self, depth: float, stretch: bool = False, **kwargs) -> Self:\n        return self.rescale_to_fit(depth, 2, stretch=stretch, **kwargs)\n\n    def set_max_width(self, max_width: float, **kwargs) -> Self:\n        if self.get_width() > max_width:\n            self.set_width(max_width, **kwargs)\n        return self\n\n    def set_max_height(self, max_height: float, **kwargs) -> Self:\n        if self.get_height() > max_height:\n            self.set_height(max_height, **kwargs)\n        return self\n\n    def set_max_depth(self, max_depth: float, **kwargs) -> Self:\n        if self.get_depth() > max_depth:\n            self.set_depth(max_depth, **kwargs)\n        return self\n\n    def set_min_width(self, min_width: float, **kwargs) -> Self:\n        if self.get_width() < min_width:\n            self.set_width(min_width, **kwargs)\n        return self\n\n    def set_min_height(self, min_height: float, **kwargs) -> Self:\n        if self.get_height() < min_height:\n            self.set_height(min_height, **kwargs)\n        return self\n\n    def set_min_depth(self, min_depth: float, **kwargs) -> Self:\n        if self.get_depth() < min_depth:\n            self.set_depth(min_depth, **kwargs)\n        return self\n\n    def set_shape(\n        self,\n        width: Optional[float] = None,\n        height: Optional[float] = None,\n        depth: Optional[float] = None,\n        **kwargs\n    ) -> Self:\n        if width is not None:\n            self.set_width(width, stretch=True, **kwargs)\n        if height is not None:\n            self.set_height(height, stretch=True, **kwargs)\n        if depth is not None:\n            self.set_depth(depth, stretch=True, **kwargs)\n        return self\n\n    def set_coord(self, value: float, dim: int, direction: Vect3 = ORIGIN) -> Self:\n        curr = self.get_coord(dim, direction)\n        shift_vect = np.zeros(self.dim)\n        shift_vect[dim] = value - curr\n        self.shift(shift_vect)\n        return self\n\n    def set_x(self, x: float, direction: Vect3 = ORIGIN) -> Self:\n        return self.set_coord(x, 0, direction)\n\n    def set_y(self, y: float, direction: Vect3 = ORIGIN) -> Self:\n        return self.set_coord(y, 1, direction)\n\n    def set_z(self, z: float, direction: Vect3 = ORIGIN) -> Self:\n        return self.set_coord(z, 2, direction)\n\n    def set_z_index(self, z_index: int, recurse=True) -> Self:\n        for mob in self.get_family(recurse):\n            mob.z_index = z_index\n        return self\n\n    def space_out_submobjects(self, factor: float = 1.5, **kwargs) -> Self:\n        self.scale(factor, **kwargs)\n        for submob in self.submobjects:\n            submob.scale(1. / factor)\n        return self\n\n    def move_to(\n        self,\n        point_or_mobject: Mobject | Vect3,\n        aligned_edge: Vect3 = ORIGIN,\n        coor_mask: Vect3 = np.array([1, 1, 1])\n    ) -> Self:\n        if isinstance(point_or_mobject, Mobject):\n            target = point_or_mobject.get_bounding_box_point(aligned_edge)\n        else:\n            target = point_or_mobject\n        point_to_align = self.get_bounding_box_point(aligned_edge)\n        self.shift((target - point_to_align) * coor_mask)\n        return self\n\n    def replace(self, mobject: Mobject, dim_to_match: int = 0, stretch: bool = False) -> Self:\n        if not mobject.get_num_points() and not mobject.submobjects:\n            self.scale(0)\n            return self\n        if stretch:\n            for i in range(self.dim):\n                self.rescale_to_fit(mobject.length_over_dim(i), i, stretch=True)\n        else:\n            self.rescale_to_fit(\n                mobject.length_over_dim(dim_to_match),\n                dim_to_match,\n                stretch=False\n            )\n        self.shift(mobject.get_center() - self.get_center())\n        return self\n\n    def surround(\n        self,\n        mobject: Mobject,\n        dim_to_match: int = 0,\n        stretch: bool = False,\n        buff: float = MED_SMALL_BUFF\n    ) -> Self:\n        self.replace(mobject, dim_to_match, stretch)\n        length = mobject.length_over_dim(dim_to_match)\n        self.scale((length + buff) / length)\n        return self\n\n    def put_start_on(self, point: Vect3) -> Self:\n        self.shift(point - self.get_start())\n        return self\n\n    def put_end_on(self, point: Vect3) -> Self:\n        self.shift(point - self.get_end())\n        return self\n\n    def put_start_and_end_on(self, start: Vect3, end: Vect3) -> Self:\n        curr_start, curr_end = self.get_start_and_end()\n        curr_vect = curr_end - curr_start\n        if np.all(curr_vect == 0):\n            raise Exception(\"Cannot position endpoints of closed loop\")\n        target_vect = end - start\n        self.scale(\n            get_norm(target_vect) / get_norm(curr_vect),\n            about_point=curr_start,\n        )\n        self.rotate(\n            angle_of_vector(target_vect) - angle_of_vector(curr_vect),\n        )\n        self.rotate(\n            np.arctan2(curr_vect[2], get_norm(curr_vect[:2])) - np.arctan2(target_vect[2], get_norm(target_vect[:2])),\n            axis=np.array([-target_vect[1], target_vect[0], 0]),\n        )\n        self.shift(start - self.get_start())\n        return self\n\n    # Color functions\n\n    @affects_family_data\n    def set_rgba_array(\n        self,\n        rgba_array: npt.ArrayLike,\n        name: str = \"rgba\",\n        recurse: bool = False\n    ) -> Self:\n        for mob in self.get_family(recurse):\n            data = mob.data if mob.get_num_points() > 0 else mob._data_defaults\n            data[name][:] = rgba_array\n        return self\n\n    def set_color_by_rgba_func(\n        self,\n        func: Callable[[Vect3Array], Vect4Array],\n        recurse: bool = True\n    ) -> Self:\n        \"\"\"\n        Func should accept an (N, 3) array and return an (N, 4) array of RGB values in [0,1]\n        \"\"\"\n        for mob in self.get_family(recurse):\n            mob.set_rgba_array(func(mob.get_points()))\n        return self\n\n    def set_color_by_rgb_func(\n        self,\n        func: Callable[[Vect3Array], Vect3Array],\n        opacity: float = 1,\n        recurse: bool = True\n    ) -> Self:\n        \"\"\"\n        Func should accept an (N, 3) array and return an (N, 3) array of RGB values in [0,1]\n        \"\"\"\n        for mob in self.get_family(recurse):\n            points = mob.get_points()\n            opacity = np.ones((points.shape[0], 1)) * opacity\n            mob.set_rgba_array(np.hstack((func(points), opacity)))\n        return self\n\n    @affects_family_data\n    def set_rgba_array_by_color(\n        self,\n        color: ManimColor | Iterable[ManimColor] | None = None,\n        opacity: float | Iterable[float] | None = None,\n        name: str = \"rgba\",\n        recurse: bool = True\n    ) -> Self:\n        for mob in self.get_family(recurse):\n            data = mob.data if mob.has_points() > 0 else mob._data_defaults\n            if color is not None:\n                rgbs = np.array(list(map(color_to_rgb, listify(color))))\n                if 1 < len(rgbs):\n                    rgbs = resize_with_interpolation(rgbs, len(data))\n                data[name][:, :3] = rgbs\n            if opacity is not None:\n                if not isinstance(opacity, (float, int, np.floating)):\n                    opacity = resize_with_interpolation(np.array(opacity), len(data))\n                data[name][:, 3] = opacity\n        return self\n\n    def set_color(\n        self,\n        color: ManimColor | Iterable[ManimColor] | None,\n        opacity: float | Iterable[float] | None = None,\n        recurse: bool = True\n    ) -> Self:\n        self.set_rgba_array_by_color(color, opacity, recurse=False)\n        # Recurse to submobjects differently from how set_rgba_array_by_color\n        # in case they implement set_color differently\n        if recurse:\n            for submob in self.submobjects:\n                submob.set_color(color, recurse=True)\n        return self\n\n    def set_opacity(\n        self,\n        opacity: float | Iterable[float] | None,\n        recurse: bool = True\n    ) -> Self:\n        self.set_rgba_array_by_color(color=None, opacity=opacity, recurse=False)\n        if recurse:\n            for submob in self.submobjects:\n                submob.set_opacity(opacity, recurse=True)\n        return self\n\n    def get_color(self) -> str:\n        return rgb_to_hex(self.data[\"rgba\"][0, :3])\n\n    def get_opacity(self) -> float:\n        return float(self.data[\"rgba\"][0, 3])\n\n    def get_opacities(self) -> float:\n        return self.data[\"rgba\"][:, 3]\n\n    def set_color_by_gradient(self, *colors: ManimColor) -> Self:\n        if self.has_points():\n            self.set_color(colors)\n        else:\n            self.set_submobject_colors_by_gradient(*colors)\n        return self\n\n    def set_submobject_colors_by_gradient(self, *colors: ManimColor, interp_by_hsl=False) -> Self:\n        if len(colors) == 0:\n            raise Exception(\"Need at least one color\")\n        elif len(colors) == 1:\n            return self.set_color(*colors)\n\n        # mobs = self.family_members_with_points()\n        mobs = self.submobjects\n        new_colors = color_gradient(colors, len(mobs), interp_by_hsl=interp_by_hsl)\n\n        for mob, color in zip(mobs, new_colors):\n            mob.set_color(color)\n        return self\n\n    def fade(self, darkness: float = 0.5, recurse: bool = True) -> Self:\n        self.set_opacity(1.0 - darkness, recurse=recurse)\n\n    def get_shading(self) -> np.ndarray:\n        return self.uniforms[\"shading\"]\n\n    def set_shading(\n        self,\n        reflectiveness: float | None = None,\n        gloss: float | None = None,\n        shadow: float | None = None,\n        recurse: bool = True\n    ) -> Self:\n        \"\"\"\n        Larger reflectiveness makes things brighter when facing the light\n        Larger shadow makes faces opposite the light darker\n        Makes parts bright where light gets reflected toward the camera\n        \"\"\"\n        for mob in self.get_family(recurse):\n            shading = mob.uniforms[\"shading\"]\n            for i, value in enumerate([reflectiveness, gloss, shadow]):\n                if value is not None:\n                    shading[i] = value\n            mob.set_uniform(shading=shading, recurse=False)\n        return self\n\n    def get_reflectiveness(self) -> float:\n        return self.get_shading()[0]\n\n    def get_gloss(self) -> float:\n        return self.get_shading()[1]\n\n    def get_shadow(self) -> float:\n        return self.get_shading()[2]\n\n    def set_reflectiveness(self, reflectiveness: float, recurse: bool = True) -> Self:\n        self.set_shading(reflectiveness=reflectiveness, recurse=recurse)\n        return self\n\n    def set_gloss(self, gloss: float, recurse: bool = True) -> Self:\n        self.set_shading(gloss=gloss, recurse=recurse)\n        return self\n\n    def set_shadow(self, shadow: float, recurse: bool = True) -> Self:\n        self.set_shading(shadow=shadow, recurse=recurse)\n        return self\n\n    # Background rectangle\n\n    def add_background_rectangle(\n        self,\n        color: ManimColor | None = None,\n        opacity: float = 1.0,\n        **kwargs\n    ) -> Self:\n        from manimlib.mobject.shape_matchers import BackgroundRectangle\n        self.background_rectangle = BackgroundRectangle(\n            self, color=color,\n            fill_opacity=opacity,\n            **kwargs\n        )\n        self.add_to_back(self.background_rectangle)\n        return self\n\n    def add_background_rectangle_to_submobjects(self, **kwargs) -> Self:\n        for submobject in self.submobjects:\n            submobject.add_background_rectangle(**kwargs)\n        return self\n\n    def add_background_rectangle_to_family_members_with_points(self, **kwargs) -> Self:\n        for mob in self.family_members_with_points():\n            mob.add_background_rectangle(**kwargs)\n        return self\n\n    # Getters\n\n    def get_bounding_box_point(self, direction: Vect3) -> Vect3:\n        bb = self.get_bounding_box()\n        indices = (np.sign(direction) + 1).astype(int)\n        return np.array([\n            bb[indices[i]][i]\n            for i in range(3)\n        ])\n\n    def get_edge_center(self, direction: Vect3) -> Vect3:\n        return self.get_bounding_box_point(direction)\n\n    def get_corner(self, direction: Vect3) -> Vect3:\n        return self.get_bounding_box_point(direction)\n\n    def get_all_corners(self):\n        bb = self.get_bounding_box()\n        return np.array([\n            [bb[indices[-i + 1]][i] for i in range(3)]\n            for indices in it.product([0, 2], repeat=3)\n        ])\n\n    def get_center(self) -> Vect3:\n        return self.get_bounding_box()[1]\n\n    def get_center_of_mass(self) -> Vect3:\n        return self.get_all_points().mean(0)\n\n    def get_boundary_point(self, direction: Vect3) -> Vect3:\n        all_points = self.get_all_points()\n        boundary_directions = all_points - self.get_center()\n        norms = np.linalg.norm(boundary_directions, axis=1)\n        boundary_directions /= np.repeat(norms, 3).reshape((len(norms), 3))\n        index = np.argmax(np.dot(boundary_directions, np.array(direction).T))\n        return all_points[index]\n\n    def get_continuous_bounding_box_point(self, direction: Vect3) -> Vect3:\n        dl, center, ur = self.get_bounding_box()\n        corner_vect = (ur - center)\n        return center + direction / np.max(np.abs(np.true_divide(\n            direction, corner_vect,\n            out=np.zeros(len(direction)),\n            where=((corner_vect) != 0)\n        )))\n\n    def get_top(self) -> Vect3:\n        return self.get_edge_center(UP)\n\n    def get_bottom(self) -> Vect3:\n        return self.get_edge_center(DOWN)\n\n    def get_right(self) -> Vect3:\n        return self.get_edge_center(RIGHT)\n\n    def get_left(self) -> Vect3:\n        return self.get_edge_center(LEFT)\n\n    def get_zenith(self) -> Vect3:\n        return self.get_edge_center(OUT)\n\n    def get_nadir(self) -> Vect3:\n        return self.get_edge_center(IN)\n\n    def length_over_dim(self, dim: int) -> float:\n        bb = self.get_bounding_box()\n        return abs((bb[2] - bb[0])[dim])\n\n    def get_width(self) -> float:\n        return self.length_over_dim(0)\n\n    def get_height(self) -> float:\n        return self.length_over_dim(1)\n\n    def get_depth(self) -> float:\n        return self.length_over_dim(2)\n\n    def get_shape(self) -> Tuple[float]:\n        return tuple(self.length_over_dim(dim) for dim in range(3))\n\n    def get_coord(self, dim: int, direction: Vect3 = ORIGIN) -> float:\n        \"\"\"\n        Meant to generalize get_x, get_y, get_z\n        \"\"\"\n        return self.get_bounding_box_point(direction)[dim]\n\n    def get_x(self, direction=ORIGIN) -> float:\n        return self.get_coord(0, direction)\n\n    def get_y(self, direction=ORIGIN) -> float:\n        return self.get_coord(1, direction)\n\n    def get_z(self, direction=ORIGIN) -> float:\n        return self.get_coord(2, direction)\n\n    def get_start(self) -> Vect3:\n        self.throw_error_if_no_points()\n        return self.get_points()[0].copy()\n\n    def get_end(self) -> Vect3:\n        self.throw_error_if_no_points()\n        return self.get_points()[-1].copy()\n\n    def get_start_and_end(self) -> tuple[Vect3, Vect3]:\n        self.throw_error_if_no_points()\n        points = self.get_points()\n        return (points[0].copy(), points[-1].copy())\n\n    def point_from_proportion(self, alpha: float) -> Vect3:\n        points = self.get_points()\n        i, subalpha = integer_interpolate(0, len(points) - 1, alpha)\n        return interpolate(points[i], points[i + 1], subalpha)\n\n    def pfp(self, alpha):\n        \"\"\"Abbreviation for point_from_proportion\"\"\"\n        return self.point_from_proportion(alpha)\n\n    def get_pieces(self, n_pieces: int) -> Group:\n        template = self.copy()\n        template.set_submobjects([])\n        alphas = np.linspace(0, 1, n_pieces + 1)\n        return Group(*[\n            template.copy().pointwise_become_partial(\n                self, a1, a2\n            )\n            for a1, a2 in zip(alphas[:-1], alphas[1:])\n        ])\n\n    def get_z_index_reference_point(self) -> Vect3:\n        # TODO, better place to define default z_index_group?\n        z_index_group = getattr(self, \"z_index_group\", self)\n        return z_index_group.get_center()\n\n    # Match other mobject properties\n\n    def match_color(self, mobject: Mobject) -> Self:\n        return self.set_color(mobject.get_color())\n\n    def match_style(self, mobject: Mobject) -> Self:\n        self.set_color(mobject.get_color())\n        self.set_opacity(mobject.get_opacity())\n        self.set_shading(*mobject.get_shading())\n        return self\n\n    def match_dim_size(self, mobject: Mobject, dim: int, **kwargs) -> Self:\n        return self.rescale_to_fit(\n            mobject.length_over_dim(dim), dim,\n            **kwargs\n        )\n\n    def match_width(self, mobject: Mobject, **kwargs) -> Self:\n        return self.match_dim_size(mobject, 0, **kwargs)\n\n    def match_height(self, mobject: Mobject, **kwargs) -> Self:\n        return self.match_dim_size(mobject, 1, **kwargs)\n\n    def match_depth(self, mobject: Mobject, **kwargs) -> Self:\n        return self.match_dim_size(mobject, 2, **kwargs)\n\n    def match_coord(\n        self,\n        mobject_or_point: Mobject | Vect3,\n        dim: int,\n        direction: Vect3 = ORIGIN\n    ) -> Self:\n        if isinstance(mobject_or_point, Mobject):\n            coord = mobject_or_point.get_coord(dim, direction)\n        else:\n            coord = mobject_or_point[dim]\n        return self.set_coord(coord, dim=dim, direction=direction)\n\n    def match_x(\n        self,\n        mobject_or_point: Mobject | Vect3,\n        direction: Vect3 = ORIGIN\n    ) -> Self:\n        return self.match_coord(mobject_or_point, 0, direction)\n\n    def match_y(\n        self,\n        mobject_or_point: Mobject | Vect3,\n        direction: Vect3 = ORIGIN\n    ) -> Self:\n        return self.match_coord(mobject_or_point, 1, direction)\n\n    def match_z(\n        self,\n        mobject_or_point: Mobject | Vect3,\n        direction: Vect3 = ORIGIN\n    ) -> Self:\n        return self.match_coord(mobject_or_point, 2, direction)\n\n    def align_to(\n        self,\n        mobject_or_point: Mobject | Vect3,\n        direction: Vect3 = ORIGIN\n    ) -> Self:\n        \"\"\"\n        Examples:\n        mob1.align_to(mob2, UP) moves mob1 vertically so that its\n        top edge lines ups with mob2's top edge.\n\n        mob1.align_to(mob2, alignment_vect = RIGHT) moves mob1\n        horizontally so that it's center is directly above/below\n        the center of mob2\n        \"\"\"\n        if isinstance(mobject_or_point, Mobject):\n            point = mobject_or_point.get_bounding_box_point(direction)\n        else:\n            point = mobject_or_point\n\n        for dim in range(self.dim):\n            if direction[dim] != 0:\n                self.set_coord(point[dim], dim, direction)\n        return self\n\n    def get_group_class(self):\n        return Group\n\n    # Alignment\n\n    def is_aligned_with(self, mobject: Mobject) -> bool:\n        if len(self.data) != len(mobject.data):\n            return False\n        if len(self.submobjects) != len(mobject.submobjects):\n            return False\n        return all(\n            sm1.is_aligned_with(sm2)\n            for sm1, sm2 in zip(self.submobjects, mobject.submobjects)\n        )\n\n    def align_data_and_family(self, mobject: Mobject) -> Self:\n        self.align_family(mobject)\n        self.align_data(mobject)\n        return self\n\n    def align_data(self, mobject: Mobject) -> Self:\n        for mob1, mob2 in zip(self.get_family(), mobject.get_family()):\n            mob1.align_points(mob2)\n        return self\n\n    def align_points(self, mobject: Mobject) -> Self:\n        max_len = max(self.get_num_points(), mobject.get_num_points())\n        for mob in (self, mobject):\n            mob.resize_points(max_len, resize_func=resize_preserving_order)\n        return self\n\n    def align_family(self, mobject: Mobject) -> Self:\n        mob1 = self\n        mob2 = mobject\n        n1 = len(mob1)\n        n2 = len(mob2)\n        if n1 != n2:\n            mob1.add_n_more_submobjects(max(0, n2 - n1))\n            mob2.add_n_more_submobjects(max(0, n1 - n2))\n        # Recurse\n        for sm1, sm2 in zip(mob1.submobjects, mob2.submobjects):\n            sm1.align_family(sm2)\n        return self\n\n    def push_self_into_submobjects(self) -> Self:\n        copy = self.copy()\n        copy.set_submobjects([])\n        self.resize_points(0)\n        self.add(copy)\n        return self\n\n    def add_n_more_submobjects(self, n: int) -> Self:\n        if n == 0:\n            return self\n\n        curr = len(self.submobjects)\n        if curr == 0:\n            # If empty, simply add n point mobjects\n            null_mob = self.copy()\n            null_mob.set_points([self.get_center()])\n            self.set_submobjects([\n                null_mob.copy()\n                for k in range(n)\n            ])\n            return self\n        target = curr + n\n        repeat_indices = (np.arange(target) * curr) // target\n        split_factors = [\n            (repeat_indices == i).sum()\n            for i in range(curr)\n        ]\n        new_submobs = []\n        for submob, sf in zip(self.submobjects, split_factors):\n            new_submobs.append(submob)\n            for k in range(1, sf):\n                new_submobs.append(submob.invisible_copy())\n        self.set_submobjects(new_submobs)\n        return self\n\n    def invisible_copy(self) -> Self:\n        return self.copy().set_opacity(0)\n\n    # Interpolate\n\n    def interpolate(\n        self,\n        mobject1: Mobject,\n        mobject2: Mobject,\n        alpha: float,\n        path_func: Callable[[np.ndarray, np.ndarray, float], np.ndarray] = straight_path\n    ) -> Self:\n        keys = [k for k in self.data.dtype.names if k not in self.locked_data_keys]\n        if keys:\n            self.note_changed_data()\n        for key in keys:\n            md1 = mobject1.data[key]\n            md2 = mobject2.data[key]\n            if key in self.const_data_keys:\n                md1 = md1[0]\n                md2 = md2[0]\n            if key in self.pointlike_data_keys:\n                self.data[key] = path_func(md1, md2, alpha)\n            else:\n                self.data[key] = (1 - alpha) * md1 + alpha * md2\n\n        for key in self.uniforms:\n            if key in self.locked_uniform_keys:\n                continue\n            if key not in mobject1.uniforms or key not in mobject2.uniforms:\n                continue\n            self.uniforms[key] = (1 - alpha) * mobject1.uniforms[key] + alpha * mobject2.uniforms[key]\n        self.bounding_box[:] = path_func(mobject1.bounding_box, mobject2.bounding_box, alpha)\n        return self\n\n    def pointwise_become_partial(self, mobject, a, b) -> Self:\n        \"\"\"\n        Set points in such a way as to become only\n        part of mobject.\n        Inputs 0 <= a < b <= 1 determine what portion\n        of mobject to become.\n        \"\"\"\n        # To be implemented in subclass\n        return self\n\n    # Locking data\n\n    def lock_data(self, keys: Iterable[str]) -> Self:\n        \"\"\"\n        To speed up some animations, particularly transformations,\n        it can be handy to acknowledge which pieces of data\n        won't change during the animation so that calls to\n        interpolate can skip this, and so that it's not\n        read into the shader_wrapper objects needlessly\n        \"\"\"\n        if self.has_updaters():\n            return self\n        self.locked_data_keys = set(keys)\n        return self\n\n    def lock_uniforms(self, keys: Iterable[str]) -> Self:\n        if self.has_updaters():\n            return self\n        self.locked_uniform_keys = set(keys)\n        return self\n\n    def lock_matching_data(self, mobject1: Mobject, mobject2: Mobject) -> Self:\n        tuples = zip(\n            self.get_family(),\n            mobject1.get_family(),\n            mobject2.get_family(),\n        )\n        for sm, sm1, sm2 in tuples:\n            if not sm.data.dtype == sm1.data.dtype == sm2.data.dtype:\n                continue\n            sm.lock_data(\n                key for key in sm.data.dtype.names\n                if arrays_match(sm1.data[key], sm2.data[key])\n            )\n            sm.lock_uniforms(\n                key for key in self.uniforms\n                if all(listify(mobject1.uniforms.get(key, 0) == mobject2.uniforms.get(key, 0)))\n            )\n            sm.const_data_keys = set(\n                key for key in sm.data.dtype.names\n                if key not in sm.locked_data_keys\n                if all(\n                    array_is_constant(mob.data[key])\n                    for mob in (sm, sm1, sm2)\n                )\n            )\n\n        return self\n\n    def unlock_data(self) -> Self:\n        for mob in self.get_family():\n            mob.locked_data_keys = set()\n            mob.const_data_keys = set()\n            mob.locked_uniform_keys = set()\n        return self\n\n    # Operations touching shader uniforms\n\n    @staticmethod\n    def affects_shader_info_id(func: Callable[..., T]) -> Callable[..., T]:\n        @wraps(func)\n        def wrapper(self, *args, **kwargs):\n            result = func(self, *args, **kwargs)\n            self.refresh_shader_wrapper_id()\n            return result\n        return wrapper\n\n    @affects_shader_info_id\n    def set_uniform(self, recurse: bool = True, **new_uniforms) -> Self:\n        for mob in self.get_family(recurse):\n            mob.uniforms.update(new_uniforms)\n        return self\n\n    @affects_shader_info_id\n    def fix_in_frame(self, recurse: bool = True) -> Self:\n        self.set_uniform(recurse, is_fixed_in_frame=1.0)\n        return self\n\n    @affects_shader_info_id\n    def unfix_from_frame(self, recurse: bool = True) -> Self:\n        self.set_uniform(recurse, is_fixed_in_frame=0.0)\n        return self\n\n    def is_fixed_in_frame(self) -> bool:\n        return bool(self.uniforms[\"is_fixed_in_frame\"])\n\n    @affects_shader_info_id\n    def apply_depth_test(self, recurse: bool = True) -> Self:\n        for mob in self.get_family(recurse):\n            mob.depth_test = True\n        return self\n\n    @affects_shader_info_id\n    def deactivate_depth_test(self, recurse: bool = True) -> Self:\n        for mob in self.get_family(recurse):\n            mob.depth_test = False\n        return self\n\n    def set_clip_plane(self, vect: Vect3, threshold: float, recurse=True) -> Self:\n        for submob in self.get_family(recurse):\n            submob.uniforms[\"clip_plane0\"] = (*vect, threshold)\n        return self\n\n    def set_clip_planes(\n        self,\n        *vect_threshold_pairs: Iterable[Tuple[Vect3, float]],\n        recurse=True\n    ) -> Self:\n        for submob in self.get_family(recurse):\n            for n in range(4):\n                submob.uniforms[f\"clip_plane{n}\"][:] = 0\n            for n, (vect, threshold) in enumerate(vect_threshold_pairs):\n                submob.uniforms[f\"clip_plane{n}\"][:] = (*vect, threshold)\n        return self\n\n    def deactivate_clip_plane(self, recurse=True) -> Self:\n        for submob in self.get_family(recurse):\n            for n in range(4):\n                submob.uniforms[f\"clip_plane{n}\"][:] = 0\n        return self\n\n    def clip_to_box(self, box: Mobject, recurse=True) -> Self:\n        return self.set_clip_planes(\n            (RIGHT, -box.get_x(LEFT)),   # keep x >= left edge\n            (LEFT, box.get_x(RIGHT)),    # keep x <= right edge\n            (UP, -box.get_y(DOWN)),      # keep y >= bottom edge\n            (DOWN, box.get_y(UP)),       # keep y <= top edge\n            recurse=recurse,\n        )\n\n    # Shader code manipulation\n\n    @affects_data\n    def replace_shader_code(self, old: str, new: str) -> Self:\n        for mob in self.get_family():\n            mob.shader_code_replacements[old] = new\n            mob.shader_wrapper = None\n        return self\n\n    def set_color_by_code(self, glsl_code: str) -> Self:\n        \"\"\"\n        Takes a snippet of code and inserts it into a\n        context which has the following variables:\n        vec4 color, vec3 point, vec3 unit_normal.\n        The code should change the color variable\n        \"\"\"\n        self.replace_shader_code(\n            \"///// INSERT COLOR FUNCTION HERE /////\",\n            glsl_code\n        )\n        return self\n\n    def set_color_by_xyz_func(\n        self,\n        glsl_snippet: str,\n        min_value: float = -5.0,\n        max_value: float = 5.0,\n        colormap: str = \"viridis\"\n    ) -> Self:\n        \"\"\"\n        Pass in a glsl expression in terms of x, y and z which returns\n        a float.\n        \"\"\"\n        # TODO, add a version of this which changes the point data instead\n        # of the shader code\n        for char in \"xyz\":\n            glsl_snippet = glsl_snippet.replace(char, \"point.\" + char)\n        rgb_list = get_colormap_list(colormap)\n        self.set_color_by_code(\n            \"color.rgb = float_to_color({}, {}, {}, {});\".format(\n                glsl_snippet,\n                float(min_value),\n                float(max_value),\n                get_colormap_code(rgb_list)\n            )\n        )\n        return self\n\n    # For shader data\n\n    def init_shader_wrapper(self, ctx: Context):\n        self.shader_wrapper = ShaderWrapper(\n            ctx=ctx,\n            vert_data=self.data,\n            shader_folder=self.shader_folder,\n            mobject_uniforms=self.uniforms,\n            texture_paths=self.texture_paths,\n            depth_test=self.depth_test,\n            render_primitive=self.render_primitive,\n            code_replacements=self.shader_code_replacements,\n        )\n\n    def refresh_shader_wrapper_id(self):\n        for submob in self.get_family():\n            if submob.shader_wrapper is not None:\n                submob.shader_wrapper.depth_test = submob.depth_test\n                submob.shader_wrapper.refresh_id()\n        for mob in (self, *self.get_ancestors()):\n            mob._data_has_changed = True\n        return self\n\n    def get_shader_wrapper(self, ctx: Context) -> ShaderWrapper:\n        if self.shader_wrapper is None:\n            self.init_shader_wrapper(ctx)\n        return self.shader_wrapper\n\n    def get_shader_wrapper_list(self, ctx: Context) -> list[ShaderWrapper]:\n        family = self.family_members_with_points()\n        batches = batch_by_property(family, lambda sm: sm.get_shader_wrapper(ctx).get_id())\n\n        result = []\n        for submobs, sid in batches:\n            shader_wrapper = submobs[0].shader_wrapper\n            data_list = [sm.get_shader_data() for sm in submobs]\n            shader_wrapper.read_in(data_list)\n            result.append(shader_wrapper)\n        return result\n\n    def get_shader_data(self) -> np.ndarray:\n        indices = self.get_shader_vert_indices()\n        if indices is not None:\n            return self.data[indices]\n        else:\n            return self.data\n\n    def get_uniforms(self):\n        return self.uniforms\n\n    def get_shader_vert_indices(self) -> Optional[np.ndarray]:\n        return None\n\n    def render(self, ctx: Context, camera_uniforms: dict):\n        if self._data_has_changed:\n            self.shader_wrappers = self.get_shader_wrapper_list(ctx)\n            self._data_has_changed = False\n        for shader_wrapper in self.shader_wrappers:\n            shader_wrapper.update_program_uniforms(camera_uniforms)\n            shader_wrapper.pre_render()\n            shader_wrapper.render()\n\n    # Event Handlers\n    \"\"\"\n        Event handling follows the Event Bubbling model of DOM in javascript.\n        Return false to stop the event bubbling.\n        To learn more visit https://www.quirksmode.org/js/events_order.html\n\n        Event Callback Argument is a callable function taking two arguments:\n            1. Mobject\n            2. EventData\n    \"\"\"\n\n    def init_event_listners(self):\n        self.event_listners: list[EventListener] = []\n\n    def add_event_listner(\n        self,\n        event_type: EventType,\n        event_callback: Callable[[Mobject, dict[str]]]\n    ):\n        event_listner = EventListener(self, event_type, event_callback)\n        self.event_listners.append(event_listner)\n        EVENT_DISPATCHER.add_listner(event_listner)\n        return self\n\n    def remove_event_listner(\n        self,\n        event_type: EventType,\n        event_callback: Callable[[Mobject, dict[str]]]\n    ):\n        event_listner = EventListener(self, event_type, event_callback)\n        while event_listner in self.event_listners:\n            self.event_listners.remove(event_listner)\n        EVENT_DISPATCHER.remove_listner(event_listner)\n        return self\n\n    def clear_event_listners(self, recurse: bool = True):\n        self.event_listners = []\n        if recurse:\n            for submob in self.submobjects:\n                submob.clear_event_listners(recurse=recurse)\n        return self\n\n    def get_event_listners(self):\n        return self.event_listners\n\n    def get_family_event_listners(self):\n        return list(it.chain(*[sm.get_event_listners() for sm in self.get_family()]))\n\n    def get_has_event_listner(self):\n        return any(\n            mob.get_event_listners()\n            for mob in self.get_family()\n        )\n\n    def add_mouse_motion_listner(self, callback):\n        self.add_event_listner(EventType.MouseMotionEvent, callback)\n\n    def remove_mouse_motion_listner(self, callback):\n        self.remove_event_listner(EventType.MouseMotionEvent, callback)\n\n    def add_mouse_press_listner(self, callback):\n        self.add_event_listner(EventType.MousePressEvent, callback)\n\n    def remove_mouse_press_listner(self, callback):\n        self.remove_event_listner(EventType.MousePressEvent, callback)\n\n    def add_mouse_release_listner(self, callback):\n        self.add_event_listner(EventType.MouseReleaseEvent, callback)\n\n    def remove_mouse_release_listner(self, callback):\n        self.remove_event_listner(EventType.MouseReleaseEvent, callback)\n\n    def add_mouse_drag_listner(self, callback):\n        self.add_event_listner(EventType.MouseDragEvent, callback)\n\n    def remove_mouse_drag_listner(self, callback):\n        self.remove_event_listner(EventType.MouseDragEvent, callback)\n\n    def add_mouse_scroll_listner(self, callback):\n        self.add_event_listner(EventType.MouseScrollEvent, callback)\n\n    def remove_mouse_scroll_listner(self, callback):\n        self.remove_event_listner(EventType.MouseScrollEvent, callback)\n\n    def add_key_press_listner(self, callback):\n        self.add_event_listner(EventType.KeyPressEvent, callback)\n\n    def remove_key_press_listner(self, callback):\n        self.remove_event_listner(EventType.KeyPressEvent, callback)\n\n    def add_key_release_listner(self, callback):\n        self.add_event_listner(EventType.KeyReleaseEvent, callback)\n\n    def remove_key_release_listner(self, callback):\n        self.remove_event_listner(EventType.KeyReleaseEvent, callback)\n\n    # Errors\n\n    def throw_error_if_no_points(self):\n        if not self.has_points():\n            message = \"Cannot call Mobject.{} \" +\\\n                      \"for a Mobject with no points\"\n            caller_name = sys._getframe(1).f_code.co_name\n            raise Exception(message.format(caller_name))\n\n\nclass Group(Mobject, Generic[SubmobjectType]):\n    def __init__(self, *mobjects: SubmobjectType | Iterable[SubmobjectType], **kwargs):\n        super().__init__(**kwargs)\n        self._ingest_args(*mobjects)\n\n    def _ingest_args(self, *args: Mobject | Iterable[Mobject]):\n        if len(args) == 0:\n            return\n        if all(isinstance(mob, Mobject) for mob in args):\n            self.add(*args)\n        elif isinstance(args[0], Iterable):\n            self.add(*args[0])\n        else:\n            raise Exception(f\"Invalid argument to Group of type {type(args[0])}\")\n\n    def __add__(self, other: Mobject | Group) -> Self:\n        assert isinstance(other, Mobject)\n        return self.add(other)\n\n    # This is just here to make linters happy with references to things like Group(...)[0]\n    def __getitem__(self, index) -> SubmobjectType:\n        return super().__getitem__(index)\n\n\nclass Point(Mobject):\n    def __init__(\n        self,\n        location: Vect3 = ORIGIN,\n        artificial_width: float = 1e-6,\n        artificial_height: float = 1e-6,\n        **kwargs\n    ):\n        self.artificial_width = artificial_width\n        self.artificial_height = artificial_height\n        super().__init__(**kwargs)\n        self.set_location(location)\n\n    def get_width(self) -> float:\n        return self.artificial_width\n\n    def get_height(self) -> float:\n        return self.artificial_height\n\n    def get_location(self) -> Vect3:\n        return self.get_points()[0].copy()\n\n    def get_bounding_box_point(self, *args, **kwargs) -> Vect3:\n        return self.get_location()\n\n    def set_location(self, new_loc: npt.ArrayLike) -> Self:\n        self.set_points(np.array(new_loc, ndmin=2, dtype=float))\n        return self\n\n\nclass _AnimationBuilder:\n    def __init__(self, mobject: Mobject):\n        self.mobject = mobject\n        self.overridden_animation = None\n        self.mobject.generate_target()\n        self.is_chaining = False\n        self.methods: list[Callable] = []\n        self.anim_args = {}\n        self.can_pass_args = True\n\n    def __getattr__(self, method_name: str):\n        method = getattr(self.mobject.target, method_name)\n        self.methods.append(method)\n        has_overridden_animation = hasattr(method, \"_override_animate\")\n\n        if (self.is_chaining and has_overridden_animation) or self.overridden_animation:\n            raise NotImplementedError(\n                \"Method chaining is currently not supported for \" + \\\n                \"overridden animations\"\n            )\n\n        def update_target(*method_args, **method_kwargs):\n            if has_overridden_animation:\n                self.overridden_animation = method._override_animate(\n                    self.mobject, *method_args, **method_kwargs\n                )\n            else:\n                method(*method_args, **method_kwargs)\n            return self\n\n        self.is_chaining = True\n        return update_target\n\n    def __call__(self, **kwargs):\n        return self.set_anim_args(**kwargs)\n\n    def __dir__(self) -> list[str]:\n        \"\"\"\n        Extend attribute list of _AnimationBuilder object to include mobject attributes\n        for better autocompletion in the IPython terminal when using interactive mode.\n        \"\"\"\n        methods = super().__dir__()\n        mobject_methods = [\n            attr for attr in dir(self.mobject)\n            if not attr.startswith('_')\n        ]\n        return sorted(set(methods+mobject_methods))\n\n    def set_anim_args(self, **kwargs):\n        '''\n        You can change the args of :class:`~manimlib.animation.transform.Transform`, such as\n\n        - ``run_time``\n        - ``time_span``\n        - ``rate_func``\n        - ``lag_ratio``\n        - ``path_arc``\n        - ``path_func``\n\n        and so on.\n        '''\n\n        if not self.can_pass_args:\n            raise ValueError(\n                \"Animation arguments can only be passed by calling ``animate`` \" + \\\n                \"or ``set_anim_args`` and can only be passed once\",\n            )\n\n        self.anim_args = kwargs\n        self.can_pass_args = False\n        return self\n\n    def build(self):\n        from manimlib.animation.transform import _MethodAnimation\n\n        if self.overridden_animation:\n            return self.overridden_animation\n\n        return _MethodAnimation(self.mobject, self.methods, **self.anim_args)\n\n\ndef override_animate(method):\n    def decorator(animation_method):\n        method._override_animate = animation_method\n        return animation_method\n\n    return decorator\n\n\nclass _UpdaterBuilder:\n    def __init__(self, mobject: Mobject):\n        self.mobject = mobject\n\n    def __getattr__(self, method_name: str):\n        def add_updater(*method_args, **method_kwargs):\n            self.mobject.add_updater(\n                lambda m: getattr(m, method_name)(*method_args, **method_kwargs)\n            )\n            return self\n        return add_updater\n\n\nclass _FunctionalUpdaterBuilder:\n    def __init__(self, mobject: Mobject):\n        self.mobject = mobject\n\n    def __getattr__(self, method_name: str):\n        def add_updater(*method_args, **method_kwargs):\n            self.mobject.add_updater(\n                lambda m: getattr(m, method_name)(\n                    *(arg() for arg in method_args),\n                    **{\n                        key: value()\n                        for key, value in method_kwargs.items()\n                    }\n                )\n            )\n            return self\n        return add_updater\n"
  },
  {
    "path": "manimlib/mobject/mobject_update_utils.py",
    "content": "from __future__ import annotations\n\nimport inspect\n\nfrom manimlib.constants import DEG\nfrom manimlib.constants import RIGHT\nfrom manimlib.mobject.mobject import Mobject\nfrom manimlib.utils.simple_functions import clip\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable\n\n    import numpy as np\n\n    from manimlib.animation.animation import Animation\n\n\ndef assert_is_mobject_method(method):\n    assert inspect.ismethod(method)\n    mobject = method.__self__\n    assert isinstance(mobject, Mobject)\n\n\ndef always(method, *args, **kwargs):\n    assert_is_mobject_method(method)\n    mobject = method.__self__\n    func = method.__func__\n    mobject.add_updater(lambda m: func(m, *args, **kwargs))\n    return mobject\n\n\ndef f_always(method, *arg_generators, **kwargs):\n    \"\"\"\n    More functional version of always, where instead\n    of taking in args, it takes in functions which output\n    the relevant arguments.\n    \"\"\"\n    assert_is_mobject_method(method)\n    mobject = method.__self__\n    func = method.__func__\n\n    def updater(mob):\n        args = [\n            arg_generator()\n            for arg_generator in arg_generators\n        ]\n        func(mob, *args, **kwargs)\n\n    mobject.add_updater(updater)\n    return mobject\n\n\ndef always_redraw(func: Callable[..., Mobject], *args, **kwargs) -> Mobject:\n    mob = func(*args, **kwargs)\n    mob.add_updater(lambda m: mob.become(func(*args, **kwargs)))\n    return mob\n\n\ndef always_shift(\n    mobject: Mobject,\n    direction: np.ndarray = RIGHT,\n    rate: float = 0.1\n) -> Mobject:\n    mobject.add_updater(\n        lambda m, dt: m.shift(dt * rate * direction)\n    )\n    return mobject\n\n\ndef always_rotate(\n    mobject: Mobject,\n    rate: float = 20 * DEG,\n    **kwargs\n) -> Mobject:\n    mobject.add_updater(\n        lambda m, dt: m.rotate(dt * rate, **kwargs)\n    )\n    return mobject\n\n\ndef turn_animation_into_updater(\n    animation: Animation,\n    cycle: bool = False,\n    **kwargs\n) -> Mobject:\n    \"\"\"\n    Add an updater to the animation's mobject which applies\n    the interpolation and update functions of the animation\n\n    If cycle is True, this repeats over and over.  Otherwise,\n    the updater will be popped uplon completion\n    \"\"\"\n    mobject = animation.mobject\n    animation.update_rate_info(**kwargs)\n    animation.suspend_mobject_updating = False\n    animation.begin()\n    animation.total_time = 0\n\n    def update(m, dt):\n        run_time = animation.get_run_time()\n        time_ratio = animation.total_time / run_time\n        if cycle:\n            alpha = time_ratio % 1\n        else:\n            alpha = clip(time_ratio, 0, 1)\n            if alpha >= 1:\n                animation.finish()\n                m.remove_updater(update)\n                return\n        animation.interpolate(alpha)\n        animation.update_mobjects(dt)\n        animation.total_time += dt\n\n    mobject.add_updater(update)\n    return mobject\n\n\ndef cycle_animation(animation: Animation, **kwargs) -> Mobject:\n    return turn_animation_into_updater(\n        animation, cycle=True, **kwargs\n    )\n"
  },
  {
    "path": "manimlib/mobject/number_line.py",
    "content": "from __future__ import annotations\n\nimport numpy as np\n\nfrom manimlib.constants import DOWN, LEFT, RIGHT, UP\nfrom manimlib.constants import DEFAULT_LIGHT_COLOR\nfrom manimlib.constants import MED_SMALL_BUFF, SMALL_BUFF\nfrom manimlib.constants import YELLOW, DEG\nfrom manimlib.mobject.geometry import Line, ArrowTip\nfrom manimlib.mobject.numbers import DecimalNumber\nfrom manimlib.mobject.svg.tex_mobject import Tex\nfrom manimlib.mobject.types.vectorized_mobject import VGroup\nfrom manimlib.mobject.value_tracker import ValueTracker\nfrom manimlib.utils.bezier import interpolate\nfrom manimlib.utils.bezier import outer_interpolate\nfrom manimlib.utils.dict_ops import merge_dicts_recursively\nfrom manimlib.utils.simple_functions import fdiv\nfrom manimlib.utils.space_ops import rotate_vector, angle_of_vector\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Iterable, Optional, Tuple, Dict, Any\n    from manimlib.typing import ManimColor, Vect3, Vect3Array, VectN, RangeSpecifier\n\n\nclass NumberLine(Line):\n    def __init__(\n        self,\n        x_range: RangeSpecifier = (-8, 8, 1),\n        color: ManimColor = DEFAULT_LIGHT_COLOR,\n        stroke_width: float = 2.0,\n        # How big is one one unit of this number line in terms of absolute spacial distance\n        unit_size: float = 1.0,\n        width: Optional[float] = None,\n        include_ticks: bool = True,\n        tick_size: float = 0.1,\n        longer_tick_multiple: float = 1.5,\n        tick_offset: float = 0.0,\n        # Change name\n        big_tick_spacing: Optional[float] = None,\n        big_tick_numbers: list[float] = [],\n        include_numbers: bool = False,\n        line_to_number_direction: Vect3 = DOWN,\n        line_to_number_buff: float = MED_SMALL_BUFF,\n        include_tip: bool = False,\n        tip_config: dict = dict(\n            width=0.25,\n            length=0.25,\n        ),\n        decimal_number_config: dict = dict(\n            num_decimal_places=0,\n            font_size=36,\n        ),\n        numbers_to_exclude: list | None = None,\n        **kwargs,\n    ):\n        self.x_range = x_range\n        self.tick_size = tick_size\n        self.longer_tick_multiple = longer_tick_multiple\n        self.tick_offset = tick_offset\n        if big_tick_spacing is not None:\n            self.big_tick_numbers = np.arange(\n                x_range[0],\n                x_range[1] + big_tick_spacing,\n                big_tick_spacing,\n            )\n        else:\n            self.big_tick_numbers = list(big_tick_numbers)\n        self.line_to_number_direction = line_to_number_direction\n        self.line_to_number_buff = line_to_number_buff\n        self.include_tip = include_tip\n        self.tip_config = dict(tip_config)\n        self.decimal_number_config = dict(decimal_number_config)\n        self.numbers_to_exclude = numbers_to_exclude\n\n        self.x_min, self.x_max = x_range[:2]\n        self.x_step = 1 if len(x_range) == 2 else x_range[2]\n\n        super().__init__(\n            self.x_min * RIGHT, self.x_max * RIGHT,\n            color=color,\n            stroke_width=stroke_width,\n            **kwargs\n        )\n\n        if width:\n            self.set_width(width)\n        else:\n            self.scale(unit_size)\n        self.center()\n\n        if include_tip:\n            self.add_tip()\n            self.tip.set_stroke(\n                self.stroke_color,\n                self.stroke_width,\n            )\n        if include_ticks:\n            self.add_ticks()\n        if include_numbers:\n            self.add_numbers(excluding=self.numbers_to_exclude)\n\n    def get_tick_range(self) -> np.ndarray:\n        if self.include_tip:\n            x_max = self.x_max\n        else:\n            x_max = self.x_max + self.x_step\n        result = np.arange(self.x_min, x_max, self.x_step)\n        return result[result <= self.x_max]\n\n    def add_ticks(self) -> None:\n        ticks = VGroup()\n        for x in self.get_tick_range():\n            size = self.tick_size\n            if np.isclose(self.big_tick_numbers, x).any():\n                size *= self.longer_tick_multiple\n            ticks.add(self.get_tick(x, size))\n        self.add(ticks)\n        self.ticks = ticks\n\n    def get_tick(self, x: float, size: float | None = None) -> Line:\n        if size is None:\n            size = self.tick_size\n        result = Line(size * DOWN, size * UP)\n        result.rotate(self.get_angle())\n        result.move_to(self.number_to_point(x))\n        result.match_style(self)\n        return result\n\n    def get_tick_marks(self) -> VGroup:\n        return self.ticks\n\n    def number_to_point(self, number: float | VectN) -> Vect3 | Vect3Array:\n        start = self.get_points()[0]\n        end = self.get_points()[-1]\n        alpha = (number - self.x_min) / (self.x_max - self.x_min)\n        return outer_interpolate(start, end, alpha)\n\n    def point_to_number(self, point: Vect3 | Vect3Array) -> float | VectN:\n        start = self.get_points()[0]\n        end = self.get_points()[-1]\n        vect = end - start\n        proportion = fdiv(\n            np.dot(point - start, vect),\n            np.dot(end - start, vect),\n        )\n        return interpolate(self.x_min, self.x_max, proportion)\n\n    def n2p(self, number: float | VectN) -> Vect3 | Vect3Array:\n        \"\"\"Abbreviation for number_to_point\"\"\"\n        return self.number_to_point(number)\n\n    def p2n(self, point: Vect3 | Vect3Array) -> float | VectN:\n        \"\"\"Abbreviation for point_to_number\"\"\"\n        return self.point_to_number(point)\n\n    def get_unit_size(self) -> float:\n        return self.get_length() / (self.x_max - self.x_min)\n\n    def get_number_mobject(\n        self,\n        x: float,\n        direction: Vect3 | None = None,\n        buff: float | None = None,\n        unit: float = 1.0,\n        unit_tex: str = \"\",\n        **number_config\n    ) -> DecimalNumber:\n        number_config = merge_dicts_recursively(\n            self.decimal_number_config, number_config,\n        )\n        if direction is None:\n            direction = self.line_to_number_direction\n        if buff is None:\n            buff = self.line_to_number_buff\n        if unit_tex:\n            number_config[\"unit\"] = unit_tex\n\n        num_mob = DecimalNumber(x / unit, **number_config)\n        num_mob.next_to(\n            self.number_to_point(x),\n            direction=direction,\n            buff=buff\n        )\n        if x < 0 and direction[0] == 0:\n            # Align without the minus sign\n            num_mob.shift(num_mob[0].get_width() * LEFT / 2)\n        if abs(x) == unit and unit_tex:\n            center = num_mob.get_center()\n            if x > 0:\n                num_mob.remove(num_mob[0])\n            else:\n                num_mob.remove(num_mob[1])\n                num_mob[0].next_to(num_mob[1], LEFT, buff=num_mob[0].get_width() / 4)\n            num_mob.move_to(center)\n        return num_mob\n\n    def add_numbers(\n        self,\n        x_values: Iterable[float] | None = None,\n        excluding: Iterable[float] | None = None,\n        font_size: int = 24,\n        **kwargs\n    ) -> VGroup:\n        if x_values is None:\n            x_values = self.get_tick_range()\n\n        kwargs[\"font_size\"] = font_size\n\n        if excluding is None:\n            excluding = self.numbers_to_exclude\n\n        numbers = VGroup()\n        for x in x_values:\n            if excluding is not None and x in excluding:\n                continue\n            numbers.add(self.get_number_mobject(x, **kwargs))\n        self.add(numbers)\n        self.numbers = numbers\n        return numbers\n\n\nclass UnitInterval(NumberLine):\n    def __init__(\n        self,\n        x_range: RangeSpecifier = (0, 1, 0.1),\n        unit_size: float = 10,\n        big_tick_numbers: list[float] = [0, 1],\n        decimal_number_config: dict = dict(\n            num_decimal_places=1,\n        ),\n        **kwargs\n    ):\n        super().__init__(\n            x_range=x_range,\n            unit_size=unit_size,\n            big_tick_numbers=big_tick_numbers,\n            decimal_number_config=decimal_number_config,\n            **kwargs\n        )\n\n\nclass Slider(VGroup):\n    def __init__(\n        self,\n        value_tracker: ValueTracker,\n        x_range: Tuple[float, float] = (-5, 5),\n        var_name: Optional[str] = None,\n        width: float = 3,\n        unit_size: float = 1,\n        arrow_width: float = 0.15,\n        arrow_length: float = 0.15,\n        arrow_color: ManimColor = YELLOW,\n        font_size: int = 24,\n        label_buff: float = SMALL_BUFF,\n        num_decimal_places: int = 2,\n        tick_size: float = 0.05,\n        number_line_config: Dict[str, Any] = dict(),\n        arrow_tip_config: Dict[str, Any] = dict(),\n        decimal_config: Dict[str, Any] = dict(),\n        angle: float = 0,\n        label_direction: Optional[np.ndarray] = None,\n        add_tick_labels: bool = True,\n        tick_label_font_size: int = 16,\n    ):\n        get_value = value_tracker.get_value\n        if label_direction is None:\n            label_direction = np.round(rotate_vector(UP, angle), 2)\n\n        # Initialize number line\n        number_line_kw = dict(x_range=x_range, width=width, tick_size=tick_size)\n        number_line_kw.update(number_line_config)\n        number_line = NumberLine(**number_line_kw)\n        number_line.rotate(angle)\n        if add_tick_labels:\n            number_line.add_numbers(\n                font_size=tick_label_font_size,\n                buff=2 * tick_size,\n                direction=-label_direction\n            )\n\n        # Initialize arrow tip\n        arrow_tip_kw = dict(\n            width=arrow_width,\n            length=arrow_length,\n            fill_color=arrow_color,\n            angle=-180 * DEG + angle_of_vector(label_direction),\n        )\n        arrow_tip_kw.update(arrow_tip_config)\n        tip = ArrowTip(**arrow_tip_kw)\n        tip.add_updater(lambda m: m.move_to(number_line.n2p(get_value()), -label_direction))\n\n        # Initialize label\n        dec_string = f\"{{:.{num_decimal_places}f}}\".format(0)\n        lhs = f\"{var_name} = \" if var_name is not None else \"\"\n        label = Tex(lhs + dec_string, font_size=font_size)\n        label[var_name].set_fill(arrow_color)\n        decimal = label.make_number_changeable(dec_string)\n        decimal.add_updater(lambda m: m.set_value(get_value()))\n        label.add_updater(lambda m: m.next_to(tip, label_direction, label_buff))\n\n        # Assemble group\n        super().__init__(number_line, tip, label)\n        self.set_stroke(behind=True)\n"
  },
  {
    "path": "manimlib/mobject/numbers.py",
    "content": "from __future__ import annotations\nfrom functools import lru_cache\n\nimport numpy as np\n\nfrom manimlib.constants import DOWN, LEFT, RIGHT, UP\nfrom manimlib.constants import DEFAULT_MOBJECT_COLOR\nfrom manimlib.mobject.svg.tex_mobject import Tex\nfrom manimlib.mobject.svg.text_mobject import Text\nfrom manimlib.mobject.types.vectorized_mobject import VMobject\nfrom manimlib.utils.paths import straight_path\nfrom manimlib.utils.bezier import interpolate\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import TypeVar, Callable\n    from manimlib.mobject.mobject import Mobject\n    from manimlib.typing import ManimColor, Vect3, Self\n\n    T = TypeVar(\"T\", bound=VMobject)\n\n\n@lru_cache()\ndef char_to_cahced_mob(char: str, **text_config):\n    if \"\\\\\" in char or char == \"i\":\n        # This is for when the \"character\" is a LaTeX command\n        # like ^\\circ or \\dots\n        return Tex(char, **text_config)\n    else:\n        return Text(char, **text_config)\n\n\nclass DecimalNumber(VMobject):\n    def __init__(\n        self,\n        number: float | complex = 0,\n        color: ManimColor = DEFAULT_MOBJECT_COLOR,\n        stroke_width: float = 0,\n        fill_opacity: float = 1.0,\n        fill_border_width: float = 0.5,\n        num_decimal_places: int = 2,\n        min_total_width: Optional[int] = 0,\n        include_sign: bool = False,\n        group_with_commas: bool = True,\n        digit_buff_per_font_unit: float = 0.001,\n        show_ellipsis: bool = False,\n        unit: str | None = None,  # Aligned to bottom unless it starts with \"^\"\n        include_background_rectangle: bool = False,\n        hide_zero_components_on_complex: bool = True,\n        edge_to_fix: Vect3 = LEFT,\n        font_size: float = 48,\n        text_config: dict = dict(),  # Do not pass in font_size here\n        **kwargs\n    ):\n        self.num_decimal_places = num_decimal_places\n        self.include_sign = include_sign\n        self.group_with_commas = group_with_commas\n        self.min_total_width = min_total_width\n        self.digit_buff_per_font_unit = digit_buff_per_font_unit\n        self.show_ellipsis = show_ellipsis\n        self.unit = unit\n        self.include_background_rectangle = include_background_rectangle\n        self.hide_zero_components_on_complex = hide_zero_components_on_complex\n        self.edge_to_fix = edge_to_fix\n        self.font_size = font_size\n        self.text_config = dict(text_config)\n\n        super().__init__(\n            color=color,\n            stroke_width=stroke_width,\n            fill_opacity=fill_opacity,\n            fill_border_width=fill_border_width,\n            **kwargs\n        )\n\n        self.set_submobjects_from_number(number)\n        self.init_colors()\n\n    def set_submobjects_from_number(self, number: float | complex) -> None:\n        # Create the submobject list\n        self.number = number\n        self.num_string = self.get_num_string(number)\n\n        # Submob_templates will be a list of cached Tex and Text mobjects,\n        # with the intent of calling .copy or .become on them\n        submob_templates = list(map(self.char_to_mob, self.num_string))\n        if self.show_ellipsis:\n            dots = self.char_to_mob(\"...\")\n            dots.arrange(RIGHT, buff=2 * dots[0].get_width())\n            submob_templates.append(dots)\n        if self.unit is not None:\n            submob_templates.append(self.char_to_mob(self.unit))\n\n        # Set internals\n        font_size = self.get_font_size()\n        if len(submob_templates) == len(self.submobjects):\n            for sm, smt in zip(self.submobjects, submob_templates):\n                sm.become(smt)\n                sm.scale(font_size / smt.font_size)\n        else:\n            self.set_submobjects([\n                smt.copy().scale(font_size / smt.font_size)\n                for smt in submob_templates\n            ])\n\n        digit_buff = self.digit_buff_per_font_unit * font_size\n        self.arrange(RIGHT, buff=digit_buff, aligned_edge=DOWN)\n\n        # Handle alignment of special characters\n        for i, c in enumerate(self.num_string):\n            if c == \"–\" and len(self.num_string) > i + 1:\n                self[i].align_to(self[i + 1], UP)\n                self[i].shift(self[i + 1].get_height() * DOWN / 2)\n            elif c == \",\":\n                self[i].shift(self[i].get_height() * DOWN / 2)\n        if self.unit and self.unit.startswith(\"^\"):\n            self[-1].align_to(self, UP)\n\n        if self.include_background_rectangle:\n            self.add_background_rectangle()\n\n    def get_num_string(self, number: float | complex) -> str:\n        if isinstance(number, complex):\n            if self.hide_zero_components_on_complex and number.imag == 0:\n                number = number.real\n                formatter = self.get_formatter()\n            elif self.hide_zero_components_on_complex and number.real == 0:\n                number = number.imag\n                formatter = self.get_formatter() + \"i\"\n            else:\n                formatter = self.get_complex_formatter()\n        else:\n            formatter = self.get_formatter()\n        if self.num_decimal_places == 0 and isinstance(number, float):\n            number = int(number)\n        num_string = formatter.format(number)\n\n        rounded_num = np.round(number, self.num_decimal_places)\n        if num_string.startswith(\"-\") and rounded_num == 0:\n            if self.include_sign:\n                num_string = \"+\" + num_string[1:]\n            else:\n                num_string = num_string[1:]\n        num_string = num_string.replace(\"-\", \"–\")\n        return num_string\n\n    def char_to_mob(self, char: str) -> Text:\n        return char_to_cahced_mob(char, **self.text_config)\n\n    def interpolate(\n        self,\n        mobject1: Mobject,\n        mobject2: Mobject,\n        alpha: float,\n        path_func: Callable[[np.ndarray, np.ndarray, float], np.ndarray] = straight_path\n    ) -> Self:\n        super().interpolate(mobject1, mobject2, alpha, path_func)\n        if hasattr(mobject1, \"font_size\") and hasattr(mobject2, \"font_size\"):\n            self.font_size = interpolate(mobject1.font_size, mobject2.font_size, alpha)\n\n    def get_font_size(self) -> float:\n        return self.font_size\n\n    def get_formatter(self, **kwargs) -> str:\n        \"\"\"\n        Configuration is based first off instance attributes,\n        but overwritten by any kew word argument.  Relevant\n        key words:\n        - include_sign\n        - group_with_commas\n        - num_decimal_places\n        - field_name (e.g. 0 or 0.real)\n        \"\"\"\n        config = dict([\n            (attr, getattr(self, attr))\n            for attr in [\n                \"include_sign\",\n                \"group_with_commas\",\n                \"num_decimal_places\",\n                \"min_total_width\",\n            ]\n        ])\n        config.update(kwargs)\n        ndp = config[\"num_decimal_places\"]\n        return \"\".join([\n            \"{\",\n            config.get(\"field_name\", \"\"),\n            \":\",\n            \"+\" if config[\"include_sign\"] else \"\",\n            \"0\" + str(config.get(\"min_total_width\", \"\")) if config.get(\"min_total_width\") else \"\",\n            \",\" if config[\"group_with_commas\"] else \"\",\n            f\".{ndp}f\" if ndp > 0 else \"d\",\n            \"}\",\n        ])\n\n    def get_complex_formatter(self, **kwargs) -> str:\n        return \"\".join([\n            self.get_formatter(field_name=\"0.real\"),\n            self.get_formatter(field_name=\"0.imag\", include_sign=True),\n            \"i\"\n        ])\n\n    def get_tex(self):\n        return self.num_string\n\n    def set_value(self, number: float | complex) -> Self:\n        move_to_point = self.get_edge_center(self.edge_to_fix)\n        style = self.family_members_with_points()[0].get_style()\n        self.set_submobjects_from_number(number)\n        self.move_to(move_to_point, self.edge_to_fix)\n        self.set_style(**style)\n        for submob in self.get_family():\n            submob.uniforms.update(self.uniforms)\n        return self\n\n    def _handle_scale_side_effects(self, scale_factor: float) -> Self:\n        self.font_size *= scale_factor\n        return self\n\n    def get_value(self) -> float | complex:\n        return self.number\n\n    def increment_value(self, delta_t: float | complex = 1) -> Self:\n        self.set_value(self.get_value() + delta_t)\n        return self\n\n\nclass Integer(DecimalNumber):\n    def __init__(\n        self,\n        number: int = 0,\n        num_decimal_places: int = 0,\n        **kwargs,\n    ):\n        super().__init__(number, num_decimal_places=num_decimal_places, **kwargs)\n\n    def get_value(self) -> int:\n        return int(np.round(super().get_value()))\n"
  },
  {
    "path": "manimlib/mobject/probability.py",
    "content": "from __future__ import annotations\n\nimport numpy as np\n\nfrom manimlib.constants import BLUE, BLUE_E, GREEN_E, GREY_B, GREY_D, MAROON_B, YELLOW\nfrom manimlib.constants import DOWN, LEFT, RIGHT, UP\nfrom manimlib.constants import MED_LARGE_BUFF, MED_SMALL_BUFF, SMALL_BUFF\nfrom manimlib.mobject.geometry import Line\nfrom manimlib.mobject.geometry import Rectangle\nfrom manimlib.mobject.mobject import Mobject\nfrom manimlib.mobject.svg.brace import Brace\nfrom manimlib.mobject.svg.tex_mobject import Tex\nfrom manimlib.mobject.svg.tex_mobject import TexText\nfrom manimlib.mobject.types.vectorized_mobject import VGroup\nfrom manimlib.utils.color import color_gradient\nfrom manimlib.utils.iterables import listify\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Iterable\n    from manimlib.typing import ManimColor\n\n\nEPSILON = 0.0001\n\n\nclass SampleSpace(Rectangle):\n    def __init__(\n        self,\n        width: float = 3,\n        height: float = 3,\n        fill_color: ManimColor = GREY_D,\n        fill_opacity: float = 1,\n        stroke_width: float = 0.5,\n        stroke_color: ManimColor = GREY_B,\n        default_label_scale_val: float = 1,\n        **kwargs,\n    ):\n        super().__init__(\n            width, height,\n            fill_color=fill_color,\n            fill_opacity=fill_opacity,\n            stroke_width=stroke_width,\n            stroke_color=stroke_color,\n            **kwargs\n        )\n        self.default_label_scale_val = default_label_scale_val\n\n    def add_title(\n        self,\n        title: str = \"Sample space\",\n        buff: float = MED_SMALL_BUFF\n    ) -> None:\n        # TODO, should this really exist in SampleSpaceScene\n        title_mob = TexText(title)\n        if title_mob.get_width() > self.get_width():\n            title_mob.set_width(self.get_width())\n        title_mob.next_to(self, UP, buff=buff)\n        self.title = title_mob\n        self.add(title_mob)\n\n    def add_label(self, label: str) -> None:\n        self.label = label\n\n    def complete_p_list(self, p_list: list[float]) -> list[float]:\n        new_p_list = listify(p_list)\n        remainder = 1.0 - sum(new_p_list)\n        if abs(remainder) > EPSILON:\n            new_p_list.append(remainder)\n        return new_p_list\n\n    def get_division_along_dimension(\n        self,\n        p_list: list[float],\n        dim: int,\n        colors: Iterable[ManimColor],\n        vect: np.ndarray\n    ) -> VGroup:\n        p_list = self.complete_p_list(p_list)\n        colors = color_gradient(colors, len(p_list))\n\n        last_point = self.get_edge_center(-vect)\n        parts = VGroup()\n        for factor, color in zip(p_list, colors):\n            part = SampleSpace()\n            part.set_fill(color, 1)\n            part.replace(self, stretch=True)\n            part.stretch(factor, dim)\n            part.move_to(last_point, -vect)\n            last_point = part.get_edge_center(vect)\n            parts.add(part)\n        return parts\n\n    def get_horizontal_division(\n        self,\n        p_list: list[float],\n        colors: Iterable[ManimColor] = [GREEN_E, BLUE_E],\n        vect: np.ndarray = DOWN\n    ) -> VGroup:\n        return self.get_division_along_dimension(p_list, 1, colors, vect)\n\n    def get_vertical_division(\n        self,\n        p_list: list[float],\n        colors: Iterable[ManimColor] = [MAROON_B, YELLOW],\n        vect: np.ndarray = RIGHT\n    ) -> VGroup:\n        return self.get_division_along_dimension(p_list, 0, colors, vect)\n\n    def divide_horizontally(self, *args, **kwargs) -> None:\n        self.horizontal_parts = self.get_horizontal_division(*args, **kwargs)\n        self.add(self.horizontal_parts)\n\n    def divide_vertically(self, *args, **kwargs) -> None:\n        self.vertical_parts = self.get_vertical_division(*args, **kwargs)\n        self.add(self.vertical_parts)\n\n    def get_subdivision_braces_and_labels(\n        self,\n        parts: VGroup,\n        labels: str,\n        direction: np.ndarray,\n        buff: float = SMALL_BUFF,\n    ) -> VGroup:\n        label_mobs = VGroup()\n        braces = VGroup()\n        for label, part in zip(labels, parts):\n            brace = Brace(\n                part, direction,\n                buff=buff\n            )\n            if isinstance(label, Mobject):\n                label_mob = label\n            else:\n                label_mob = Tex(label)\n                label_mob.scale(self.default_label_scale_val)\n            label_mob.next_to(brace, direction, buff)\n\n            braces.add(brace)\n            label_mobs.add(label_mob)\n        parts.braces = braces\n        parts.labels = label_mobs\n        parts.label_kwargs = {\n            \"labels\": label_mobs.copy(),\n            \"direction\": direction,\n            \"buff\": buff,\n        }\n        return VGroup(parts.braces, parts.labels)\n\n    def get_side_braces_and_labels(\n        self,\n        labels: str,\n        direction: np.ndarray = LEFT,\n        **kwargs\n    ) -> VGroup:\n        assert hasattr(self, \"horizontal_parts\")\n        parts = self.horizontal_parts\n        return self.get_subdivision_braces_and_labels(parts, labels, direction, **kwargs)\n\n    def get_top_braces_and_labels(\n        self,\n        labels: str,\n        **kwargs\n    ) -> VGroup:\n        assert hasattr(self, \"vertical_parts\")\n        parts = self.vertical_parts\n        return self.get_subdivision_braces_and_labels(parts, labels, UP, **kwargs)\n\n    def get_bottom_braces_and_labels(\n        self,\n        labels: str,\n        **kwargs\n    ) -> VGroup:\n        assert hasattr(self, \"vertical_parts\")\n        parts = self.vertical_parts\n        return self.get_subdivision_braces_and_labels(parts, labels, DOWN, **kwargs)\n\n    def add_braces_and_labels(self) -> None:\n        for attr in \"horizontal_parts\", \"vertical_parts\":\n            if not hasattr(self, attr):\n                continue\n            parts = getattr(self, attr)\n            for subattr in \"braces\", \"labels\":\n                if hasattr(parts, subattr):\n                    self.add(getattr(parts, subattr))\n\n    def __getitem__(self, index: int | slice) -> VGroup:\n        if hasattr(self, \"horizontal_parts\"):\n            return self.horizontal_parts[index]\n        elif hasattr(self, \"vertical_parts\"):\n            return self.vertical_parts[index]\n        return self.split()[index]\n\n\nclass BarChart(VGroup):\n    def __init__(\n        self,\n        values: Iterable[float],\n        height: float = 4,\n        width: float = 6,\n        n_ticks: int = 4,\n        include_x_ticks: bool = False,\n        tick_width: float = 0.2,\n        tick_height: float = 0.15,\n        label_y_axis: bool = True,\n        y_axis_label_height: float = 0.25,\n        max_value: float = 1,\n        bar_colors: list[ManimColor] = [BLUE, YELLOW],\n        bar_fill_opacity: float = 0.8,\n        bar_stroke_width: float = 3,\n        bar_names: list[str] = [],\n        bar_label_scale_val: float = 0.75,\n        **kwargs\n    ):\n        super().__init__(**kwargs)\n        self.height = height\n        self.width = width\n        self.n_ticks = n_ticks\n        self.include_x_ticks = include_x_ticks\n        self.tick_width = tick_width\n        self.tick_height = tick_height\n        self.label_y_axis = label_y_axis\n        self.y_axis_label_height = y_axis_label_height\n        self.max_value = max_value\n        self.bar_colors = bar_colors\n        self.bar_fill_opacity = bar_fill_opacity\n        self.bar_stroke_width = bar_stroke_width\n        self.bar_names = bar_names\n        self.bar_label_scale_val = bar_label_scale_val\n\n        if self.max_value is None:\n            self.max_value = max(values)\n\n        self.n_ticks_x = len(values)\n        self.add_axes()\n        self.add_bars(values)\n        self.center()\n\n    def add_axes(self) -> None:\n        x_axis = Line(self.tick_width * LEFT / 2, self.width * RIGHT)\n        y_axis = Line(MED_LARGE_BUFF * DOWN, self.height * UP)\n        y_ticks = VGroup()\n        heights = np.linspace(0, self.height, self.n_ticks + 1)\n        values = np.linspace(0, self.max_value, self.n_ticks + 1)\n        for y, value in zip(heights, values):\n            y_tick = Line(LEFT, RIGHT)\n            y_tick.set_width(self.tick_width)\n            y_tick.move_to(y * UP)\n            y_ticks.add(y_tick)\n        y_axis.add(y_ticks)\n\n        if self.include_x_ticks == True:\n            x_ticks = VGroup()\n            widths = np.linspace(0, self.width, self.n_ticks_x + 1)\n            label_values = np.linspace(0, len(self.bar_names), self.n_ticks_x + 1)\n            for x, value in zip(widths, label_values):\n                x_tick = Line(UP, DOWN)\n                x_tick.set_height(self.tick_height)\n                x_tick.move_to(x * RIGHT)\n                x_ticks.add(x_tick)\n            x_axis.add(x_ticks)\n\n        self.add(x_axis, y_axis)\n        self.x_axis, self.y_axis = x_axis, y_axis\n\n        if self.label_y_axis:\n            labels = VGroup()\n            for y_tick, value in zip(y_ticks, values):\n                label = Tex(str(np.round(value, 2)))\n                label.set_height(self.y_axis_label_height)\n                label.next_to(y_tick, LEFT, SMALL_BUFF)\n                labels.add(label)\n            self.y_axis_labels = labels\n            self.add(labels)\n\n    def add_bars(self, values: Iterable[float]) -> None:\n        buff = float(self.width) / (2 * len(values))\n        bars = VGroup()\n        for i, value in enumerate(values):\n            bar = Rectangle(\n                height=(value / self.max_value) * self.height,\n                width=buff,\n                stroke_width=self.bar_stroke_width,\n                fill_opacity=self.bar_fill_opacity,\n            )\n            bar.move_to((2 * i + 0.5) * buff * RIGHT, DOWN + LEFT * 5)\n            bars.add(bar)\n        bars.set_color_by_gradient(*self.bar_colors)\n\n        bar_labels = VGroup()\n        for bar, name in zip(bars, self.bar_names):\n            label = Tex(str(name))\n            label.scale(self.bar_label_scale_val)\n            label.next_to(bar, DOWN, SMALL_BUFF)\n            bar_labels.add(label)\n\n        self.add(bars, bar_labels)\n        self.bars = bars\n        self.bar_labels = bar_labels\n\n    def change_bar_values(self, values: Iterable[float]) -> None:\n        for bar, value in zip(self.bars, values):\n            bar_bottom = bar.get_bottom()\n            bar.stretch_to_fit_height(\n                (value / self.max_value) * self.height\n            )\n            bar.move_to(bar_bottom, DOWN)\n"
  },
  {
    "path": "manimlib/mobject/shape_matchers.py",
    "content": "from __future__ import annotations\n\nfrom colour import Color\n\nfrom manimlib.config import manim_config\nfrom manimlib.constants import BLACK, RED, YELLOW, DEFAULT_MOBJECT_COLOR\nfrom manimlib.constants import DL, DOWN, DR, LEFT, RIGHT, UL, UR\nfrom manimlib.constants import SMALL_BUFF\nfrom manimlib.mobject.geometry import Line\nfrom manimlib.mobject.geometry import Rectangle\nfrom manimlib.mobject.types.vectorized_mobject import VGroup\nfrom manimlib.mobject.types.vectorized_mobject import VMobject\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Sequence\n    from manimlib.mobject.mobject import Mobject\n    from manimlib.typing import ManimColor, Self\n\n\nclass SurroundingRectangle(Rectangle):\n    def __init__(\n        self,\n        mobject: Mobject,\n        buff: float = SMALL_BUFF,\n        color: ManimColor = YELLOW,\n        **kwargs\n    ):\n        super().__init__(color=color, **kwargs)\n        self.buff = buff\n        self.surround(mobject)\n        if mobject.is_fixed_in_frame():\n            self.fix_in_frame()\n\n    def surround(self, mobject, buff=None) -> Self:\n        self.mobject = mobject\n        self.buff = buff if buff is not None else self.buff\n        super().surround(mobject, self.buff)\n        return self\n\n    def set_buff(self, buff) -> Self:\n        self.buff = buff\n        self.surround(self.mobject)\n        return self\n\n\nclass BackgroundRectangle(SurroundingRectangle):\n    def __init__(\n        self,\n        mobject: Mobject,\n        color: ManimColor = None,\n        stroke_width: float = 0,\n        stroke_opacity: float = 0,\n        fill_opacity: float = 0.75,\n        buff: float = 0,\n        **kwargs\n    ):\n        if color is None:\n            color = manim_config.camera.background_color\n        super().__init__(\n            mobject,\n            color=color,\n            stroke_width=stroke_width,\n            stroke_opacity=stroke_opacity,\n            fill_opacity=fill_opacity,\n            buff=buff,\n            **kwargs\n        )\n        self.original_fill_opacity = fill_opacity\n\n    def pointwise_become_partial(self, mobject: Mobject, a: float, b: float) -> Self:\n        self.set_fill(opacity=b * self.original_fill_opacity)\n        return self\n\n    def set_style(\n        self,\n        stroke_color: ManimColor | None = None,\n        stroke_width: float | None = None,\n        fill_color: ManimColor | None = None,\n        fill_opacity: float | None = None,\n        family: bool = True,\n        **kwargs\n    ) -> Self:\n        # Unchangeable style, except for fill_opacity\n        VMobject.set_style(\n            self,\n            stroke_color=BLACK,\n            stroke_width=0,\n            fill_color=BLACK,\n            fill_opacity=fill_opacity\n        )\n        return self\n\n    def get_fill_color(self) -> Color:\n        return Color(self.color)\n\n\nclass Cross(VGroup):\n    def __init__(\n        self,\n        mobject: Mobject,\n        stroke_color: ManimColor = RED,\n        stroke_width: float | Sequence[float] = [0, 6, 0],\n        **kwargs\n    ):\n        super().__init__(\n            Line(UL, DR),\n            Line(UR, DL),\n        )\n        self.insert_n_curves(20)\n        self.replace(mobject, stretch=True)\n        self.set_stroke(stroke_color, width=stroke_width)\n\n\nclass Underline(Line):\n    def __init__(\n        self,\n        mobject: Mobject,\n        buff: float = SMALL_BUFF,\n        stroke_color=DEFAULT_MOBJECT_COLOR,\n        stroke_width: float | Sequence[float] = [0, 3, 3, 0],\n        stretch_factor=1.2,\n        **kwargs\n    ):\n        super().__init__(LEFT, RIGHT, **kwargs)\n        if not isinstance(stroke_width, (float, int)):\n            self.insert_n_curves(len(stroke_width) - 2)\n        self.set_stroke(stroke_color, stroke_width)\n        self.set_width(mobject.get_width() * stretch_factor)\n        self.next_to(mobject, DOWN, buff=buff)\n"
  },
  {
    "path": "manimlib/mobject/svg/__init__.py",
    "content": ""
  },
  {
    "path": "manimlib/mobject/svg/brace.py",
    "content": "from __future__ import annotations\n\nimport math\nimport copy\n\nimport numpy as np\n\nfrom manimlib.constants import DEFAULT_MOBJECT_TO_MOBJECT_BUFF, SMALL_BUFF\nfrom manimlib.constants import DOWN, LEFT, ORIGIN, RIGHT, DL, DR, UL, UP\nfrom manimlib.constants import PI\nfrom manimlib.animation.composition import AnimationGroup\nfrom manimlib.animation.fading import FadeIn\nfrom manimlib.animation.growing import GrowFromCenter\nfrom manimlib.mobject.svg.tex_mobject import Tex\nfrom manimlib.mobject.svg.tex_mobject import TexText\nfrom manimlib.mobject.svg.text_mobject import Text\nfrom manimlib.mobject.types.vectorized_mobject import VGroup\nfrom manimlib.mobject.types.vectorized_mobject import VMobject\nfrom manimlib.utils.iterables import listify\nfrom manimlib.utils.space_ops import get_norm\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Iterable\n\n    from manimlib.animation.animation import Animation\n    from manimlib.mobject.mobject import Mobject\n    from manimlib.typing import Vect3\n\n\nclass Brace(Tex):\n    def __init__(\n        self,\n        mobject: Mobject,\n        direction: Vect3 = DOWN,\n        buff: float = 0.2,\n        tex_string: str = R\"\\underbrace{\\qquad}\",\n        **kwargs\n    ):\n        super().__init__(tex_string, **kwargs)\n\n        angle = -math.atan2(*direction[:2]) + PI\n        mobject.rotate(-angle, about_point=ORIGIN)\n        left = mobject.get_corner(DL)\n        right = mobject.get_corner(DR)\n        target_width = right[0] - left[0]\n\n        self.tip_point_index = np.argmin(self.get_all_points()[:, 1])\n        self.set_initial_width(target_width)\n        self.shift(left - self.get_corner(UL) + buff * DOWN)\n        for mob in mobject, self:\n            mob.rotate(angle, about_point=ORIGIN)\n\n    def set_initial_width(self, width: float):\n        width_diff = width - self.get_width()\n        if width_diff > 0:\n            for tip, rect, vect in [(self[0], self[1], RIGHT), (self[5], self[4], LEFT)]:\n                rect.set_width(\n                    width_diff / 2 + rect.get_width(),\n                    about_edge=vect, stretch=True\n                )\n                tip.shift(-width_diff / 2 * vect)\n        else:\n            self.set_width(width, stretch=True)\n        return self\n\n    def put_at_tip(\n        self,\n        mob: Mobject,\n        use_next_to: bool = True,\n        **kwargs\n    ):\n        if use_next_to:\n            mob.next_to(\n                self.get_tip(),\n                np.round(self.get_direction()),\n                **kwargs\n            )\n        else:\n            mob.move_to(self.get_tip())\n            buff = kwargs.get(\"buff\", DEFAULT_MOBJECT_TO_MOBJECT_BUFF)\n            shift_distance = mob.get_width() / 2.0 + buff\n            mob.shift(self.get_direction() * shift_distance)\n        return self\n\n    def get_text(self, text: str, **kwargs) -> Text:\n        buff = kwargs.pop(\"buff\", SMALL_BUFF)\n        text_mob = Text(text, **kwargs)\n        self.put_at_tip(text_mob, buff=buff)\n        return text_mob\n\n    def get_tex(self, *tex: str, **kwargs) -> Tex:\n        buff = kwargs.pop(\"buff\", SMALL_BUFF)\n        tex_mob = Tex(*tex, **kwargs)\n        self.put_at_tip(tex_mob, buff=buff)\n        return tex_mob\n\n    def get_tip(self) -> np.ndarray:\n        # Very specific to the LaTeX representation\n        # of a brace, but it's the only way I can think\n        # of to get the tip regardless of orientation.\n        return self.get_all_points()[self.tip_point_index]\n\n    def get_direction(self) -> np.ndarray:\n        vect = self.get_tip() - self.get_center()\n        return vect / get_norm(vect)\n\n\nclass BraceLabel(VMobject):\n    label_constructor: type = Tex\n\n    def __init__(\n        self,\n        obj: VMobject | list[VMobject],\n        text: str | Iterable[str],\n        brace_direction: np.ndarray = DOWN,\n        label_scale: float = 1.0,\n        label_buff: float = DEFAULT_MOBJECT_TO_MOBJECT_BUFF,\n        **kwargs\n    ) -> None:\n        super().__init__(**kwargs)\n        self.brace_direction = brace_direction\n        self.label_scale = label_scale\n        self.label_buff = label_buff\n\n        if isinstance(obj, list):\n            obj = VGroup(*obj)\n        self.brace = Brace(obj, brace_direction, **kwargs)\n\n        self.label = self.label_constructor(*listify(text), **kwargs)\n        self.label.scale(self.label_scale)\n\n        self.brace.put_at_tip(self.label, buff=self.label_buff)\n        self.set_submobjects([self.brace, self.label])\n\n    def creation_anim(\n        self,\n        label_anim: Animation = FadeIn,\n        brace_anim: Animation = GrowFromCenter\n    ) -> AnimationGroup:\n        return AnimationGroup(brace_anim(self.brace), label_anim(self.label))\n\n    def shift_brace(self, obj: VMobject | list[VMobject], **kwargs):\n        if isinstance(obj, list):\n            obj = VMobject(*obj)\n        self.brace = Brace(obj, self.brace_direction, **kwargs)\n        self.brace.put_at_tip(self.label)\n        self.submobjects[0] = self.brace\n        return self\n\n    def change_label(self, *text: str, **kwargs):\n        self.label = self.label_constructor(*text, **kwargs)\n        if self.label_scale != 1:\n            self.label.scale(self.label_scale)\n\n        self.brace.put_at_tip(self.label)\n        self.submobjects[1] = self.label\n        return self\n\n    def change_brace_label(self, obj: VMobject | list[VMobject], *text: str):\n        self.shift_brace(obj)\n        self.change_label(*text)\n        return self\n\n    def copy(self):\n        copy_mobject = copy.copy(self)\n        copy_mobject.brace = self.brace.copy()\n        copy_mobject.label = self.label.copy()\n        copy_mobject.set_submobjects([copy_mobject.brace, copy_mobject.label])\n\n        return copy_mobject\n\n\nclass BraceText(BraceLabel):\n    label_constructor: type = TexText\n\n\nclass LineBrace(Brace):\n    def __init__(self, line: Line, direction=UP, **kwargs):\n        angle = line.get_angle()\n        line.rotate(-angle)\n        super().__init__(line, direction, **kwargs)\n        line.rotate(angle)\n        self.rotate(angle, about_point=line.get_center())\n"
  },
  {
    "path": "manimlib/mobject/svg/drawings.py",
    "content": "from __future__ import annotations\n\nimport numpy as np\nimport itertools as it\nimport random\nimport math\n\nfrom manimlib.animation.composition import AnimationGroup\nfrom manimlib.animation.rotation import Rotating\nfrom manimlib.constants import BLACK\nfrom manimlib.constants import BLUE_A\nfrom manimlib.constants import BLUE_B\nfrom manimlib.constants import BLUE_C\nfrom manimlib.constants import BLUE_D\nfrom manimlib.constants import DOWN\nfrom manimlib.constants import DOWN\nfrom manimlib.constants import FRAME_WIDTH\nfrom manimlib.constants import GREEN\nfrom manimlib.constants import GREEN_SCREEN\nfrom manimlib.constants import GREEN_E\nfrom manimlib.constants import GREY\nfrom manimlib.constants import GREY_A\nfrom manimlib.constants import GREY_B\nfrom manimlib.constants import GREY_E\nfrom manimlib.constants import LEFT\nfrom manimlib.constants import LEFT\nfrom manimlib.constants import MED_LARGE_BUFF\nfrom manimlib.constants import MED_SMALL_BUFF\nfrom manimlib.constants import ORIGIN\nfrom manimlib.constants import OUT\nfrom manimlib.constants import PI\nfrom manimlib.constants import RED\nfrom manimlib.constants import RED_E\nfrom manimlib.constants import RIGHT\nfrom manimlib.constants import SMALL_BUFF\nfrom manimlib.constants import SMALL_BUFF\nfrom manimlib.constants import UP\nfrom manimlib.constants import UL\nfrom manimlib.constants import UR\nfrom manimlib.constants import DL\nfrom manimlib.constants import DR\nfrom manimlib.constants import WHITE\nfrom manimlib.constants import YELLOW\nfrom manimlib.constants import TAU\nfrom manimlib.mobject.boolean_ops import Difference\nfrom manimlib.mobject.boolean_ops import Union\nfrom manimlib.mobject.geometry import Arc\nfrom manimlib.mobject.geometry import Circle\nfrom manimlib.mobject.geometry import Dot\nfrom manimlib.mobject.geometry import Line\nfrom manimlib.mobject.geometry import Polygon\nfrom manimlib.mobject.geometry import Rectangle\nfrom manimlib.mobject.geometry import Square\nfrom manimlib.mobject.geometry import AnnularSector\nfrom manimlib.mobject.numbers import Integer\nfrom manimlib.mobject.shape_matchers import SurroundingRectangle\nfrom manimlib.mobject.svg.svg_mobject import SVGMobject\nfrom manimlib.mobject.svg.special_tex import TexTextFromPresetString\nfrom manimlib.mobject.three_dimensions import Prismify\nfrom manimlib.mobject.three_dimensions import VCube\nfrom manimlib.mobject.types.vectorized_mobject import VGroup\nfrom manimlib.mobject.types.vectorized_mobject import VMobject\nfrom manimlib.mobject.svg.text_mobject import Text\nfrom manimlib.utils.bezier import interpolate\nfrom manimlib.utils.iterables import adjacent_pairs\nfrom manimlib.utils.rate_functions import linear\nfrom manimlib.utils.space_ops import angle_of_vector\nfrom manimlib.utils.space_ops import compass_directions\nfrom manimlib.utils.space_ops import get_norm\nfrom manimlib.utils.space_ops import midpoint\nfrom manimlib.utils.space_ops import rotate_vector\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Tuple, Sequence, Callable\n    from manimlib.typing import ManimColor, Vect3\n\n\nclass Checkmark(TexTextFromPresetString):\n    tex: str = R\"\\ding{51}\"\n    default_color: ManimColor = GREEN\n\n\nclass Exmark(TexTextFromPresetString):\n    tex: str = R\"\\ding{55}\"\n    default_color: ManimColor = RED\n\n\nclass Lightbulb(SVGMobject):\n    file_name = \"lightbulb\"\n\n    def __init__(\n        self,\n        height: float = 1.0,\n        color: ManimColor = YELLOW,\n        stroke_width: float = 3.0,\n        fill_opacity: float = 0.0,\n        **kwargs\n    ):\n        super().__init__(\n            height=height,\n            color=color,\n            stroke_width=stroke_width,\n            fill_opacity=fill_opacity,\n            **kwargs\n        )\n        self.insert_n_curves(25)\n\n\nclass Speedometer(VMobject):\n    def __init__(\n        self,\n        arc_angle: float = 4 * PI / 3,\n        num_ticks: int = 8,\n        tick_length: float = 0.2,\n        needle_width: float = 0.1,\n        needle_height: float = 0.8,\n        needle_color: ManimColor = YELLOW,\n        **kwargs,\n    ):\n        super().__init__(**kwargs)\n\n        self.arc_angle = arc_angle\n        self.num_ticks = num_ticks\n        self.tick_length = tick_length\n        self.needle_width = needle_width\n        self.needle_height = needle_height\n        self.needle_color = needle_color\n\n        start_angle = PI / 2 + arc_angle / 2\n        end_angle = PI / 2 - arc_angle / 2\n        self.arc = Arc(\n            start_angle=start_angle,\n            angle=-self.arc_angle\n        )\n        self.add(self.arc)\n        tick_angle_range = np.linspace(start_angle, end_angle, num_ticks)\n        for index, angle in enumerate(tick_angle_range):\n            vect = rotate_vector(RIGHT, angle)\n            tick = Line((1 - tick_length) * vect, vect)\n            label = Integer(10 * index)\n            label.set_height(tick_length)\n            label.shift((1 + tick_length) * vect)\n            self.add(tick, label)\n\n        needle = Polygon(\n            LEFT, UP, RIGHT,\n            stroke_width=0,\n            fill_opacity=1,\n            fill_color=self.needle_color\n        )\n        needle.stretch_to_fit_width(needle_width)\n        needle.stretch_to_fit_height(needle_height)\n        needle.rotate(start_angle - np.pi / 2, about_point=ORIGIN)\n        self.add(needle)\n        self.needle = needle\n\n        self.center_offset = self.get_center()\n\n    def get_center(self):\n        result = VMobject.get_center(self)\n        if hasattr(self, \"center_offset\"):\n            result -= self.center_offset\n        return result\n\n    def get_needle_tip(self):\n        return self.needle.get_anchors()[1]\n\n    def get_needle_angle(self):\n        return angle_of_vector(\n            self.get_needle_tip() - self.get_center()\n        )\n\n    def rotate_needle(self, angle):\n        self.needle.rotate(angle, about_point=self.arc.get_arc_center())\n        return self\n\n    def move_needle_to_velocity(self, velocity):\n        max_velocity = 10 * (self.num_ticks - 1)\n        proportion = float(velocity) / max_velocity\n        start_angle = np.pi / 2 + self.arc_angle / 2\n        target_angle = start_angle - self.arc_angle * proportion\n        self.rotate_needle(target_angle - self.get_needle_angle())\n        return self\n\n\nclass Laptop(VGroup):\n    def __init__(\n        self,\n        width: float = 3,\n        body_dimensions: Tuple[float, float, float] = (4.0, 3.0, 0.05),\n        screen_thickness: float = 0.01,\n        keyboard_width_to_body_width: float = 0.9,\n        keyboard_height_to_body_height: float = 0.5,\n        screen_width_to_screen_plate_width: float = 0.9,\n        key_color_kwargs: dict = dict(\n            stroke_width=0,\n            fill_color=BLACK,\n            fill_opacity=1,\n        ),\n        fill_opacity: float = 1.0,\n        stroke_width: float = 0.0,\n        body_color: ManimColor = GREY_B,\n        shaded_body_color: ManimColor = GREY,\n        open_angle: float = np.pi / 4,\n        **kwargs\n    ):\n        super().__init__(**kwargs)\n\n        body = VCube(side_length=1)\n        for dim, scale_factor in enumerate(body_dimensions):\n            body.stretch(scale_factor, dim=dim)\n        body.set_width(width)\n        body.set_fill(shaded_body_color, opacity=1)\n        body.sort(lambda p: p[2])\n        body[-1].set_fill(body_color)\n        screen_plate = body.copy()\n        keyboard = VGroup(*[\n            VGroup(*[\n                Square(**key_color_kwargs)\n                for x in range(12 - y % 2)\n            ]).arrange(RIGHT, buff=SMALL_BUFF)\n            for y in range(4)\n        ]).arrange(DOWN, buff=MED_SMALL_BUFF)\n        keyboard.stretch_to_fit_width(\n            keyboard_width_to_body_width * body.get_width(),\n        )\n        keyboard.stretch_to_fit_height(\n            keyboard_height_to_body_height * body.get_height(),\n        )\n        keyboard.next_to(body, OUT, buff=0.1 * SMALL_BUFF)\n        keyboard.shift(MED_SMALL_BUFF * UP)\n        body.add(keyboard)\n\n        screen_plate.stretch(screen_thickness /\n                             body_dimensions[2], dim=2)\n        screen = Rectangle(\n            stroke_width=0,\n            fill_color=BLACK,\n            fill_opacity=1,\n        )\n        screen.replace(screen_plate, stretch=True)\n        screen.scale(screen_width_to_screen_plate_width)\n        screen.next_to(screen_plate, OUT, buff=0.1 * SMALL_BUFF)\n        screen_plate.add(screen)\n        screen_plate.next_to(body, UP, buff=0)\n        screen_plate.rotate(\n            open_angle, RIGHT,\n            about_point=screen_plate.get_bottom()\n        )\n        self.screen_plate = screen_plate\n        self.screen = screen\n\n        axis = Line(\n            body.get_corner(UP + LEFT + OUT),\n            body.get_corner(UP + RIGHT + OUT),\n            color=BLACK,\n            stroke_width=2\n        )\n        self.axis = axis\n\n        self.add(body, screen_plate, axis)\n\n\nclass VideoIcon(SVGMobject):\n    file_name: str = \"video_icon\"\n\n    def __init__(\n        self,\n        width: float = 1.2,\n        color=BLUE_A,\n        **kwargs\n    ):\n        super().__init__(color=color, **kwargs)\n        self.set_width(width)\n\n\nclass VideoSeries(VGroup):\n    def __init__(\n        self,\n        num_videos: int = 11,\n        gradient_colors: Sequence[ManimColor] = [BLUE_B, BLUE_D],\n        width: float = FRAME_WIDTH - MED_LARGE_BUFF,\n        **kwargs\n    ):\n        super().__init__(\n            *(VideoIcon() for x in range(num_videos)),\n            **kwargs\n        )\n        self.arrange(RIGHT)\n        self.set_width(width)\n        self.set_color_by_gradient(*gradient_colors)\n\n\nclass Clock(VGroup):\n    def __init__(\n        self,\n        stroke_color: ManimColor = WHITE,\n        stroke_width: float = 3.0,\n        hour_hand_height: float = 0.3,\n        minute_hand_height: float = 0.6,\n        tick_length: float = 0.1,\n        **kwargs,\n    ):\n        style = dict(stroke_color=stroke_color, stroke_width=stroke_width)\n        circle = Circle(**style)\n        self.ticks = VGroup()\n        for x, angle in enumerate(np.arange(0, TAU, TAU / 12)):\n            point = math.cos(angle) * UP + math.sin(angle) * RIGHT\n            length = tick_length\n            if x % 3 == 0:\n                length *= 2\n            self.ticks.add(Line(point, (1 - length) * point, **style))\n        self.hour_hand = Line(ORIGIN, hour_hand_height * UP, **style)\n        self.minute_hand = Line(ORIGIN, minute_hand_height * UP, **style)\n\n        super().__init__(\n            circle, self.hour_hand, self.minute_hand, self.ticks\n        )\n\n\nclass ClockPassesTime(AnimationGroup):\n    def __init__(\n        self,\n        clock: Clock,\n        run_time: float = 5.0,\n        hours_passed: float = 12.0,\n        rate_func: Callable[[float], float] = linear,\n        **kwargs\n    ):\n        rot_kwargs = dict(\n            axis=OUT,\n            about_point=clock.get_center()\n        )\n        hour_radians = -hours_passed * 2 * PI / 12\n        super().__init__(\n            Rotating(\n                clock.hour_hand,\n                angle=hour_radians,\n                **rot_kwargs\n            ),\n            Rotating(\n                clock.minute_hand,\n                angle=12 * hour_radians,\n                **rot_kwargs\n            ),\n            group=clock,\n            run_time=run_time,\n            **kwargs\n        )\n\n\nclass Bubble(VGroup):\n    file_name: str = \"Bubbles_speech.svg\"\n    bubble_center_adjustment_factor = 0.125\n\n    def __init__(\n        self,\n        content: str | VMobject | None = None,\n        buff: float = 1.0,\n        filler_shape: Tuple[float, float] = (3.0, 2.0),\n        pin_point: Vect3 | None = None,\n        direction: Vect3 = LEFT,\n        add_content: bool = True,\n        fill_color: ManimColor = BLACK,\n        fill_opacity: float = 0.8,\n        stroke_color: ManimColor = WHITE,\n        stroke_width: float = 3.0,\n        **kwargs\n    ):\n        super().__init__(**kwargs)\n        self.direction = direction\n\n        if content is None:\n            content = Rectangle(*filler_shape)\n            content.set_fill(opacity=0)\n            content.set_stroke(width=0)\n        elif isinstance(content, str):\n            content = Text(content)\n        self.content = content\n\n        self.body = self.get_body(content, direction, buff)\n        self.body.set_fill(fill_color, fill_opacity)\n        self.body.set_stroke(stroke_color, stroke_width)\n        self.add(self.body)\n\n        if add_content:\n            self.add(self.content)\n\n        if pin_point is not None:\n            self.pin_to(pin_point)\n\n    def get_body(self, content: VMobject, direction: Vect3, buff: float) -> VMobject:\n        body = SVGMobject(self.file_name)\n        if direction[0] > 0:\n            body.flip()\n        # Resize\n        width = content.get_width()\n        height = content.get_height()\n        target_width = width + min(buff, height)\n        target_height = 1.35 * (height + buff)  # Magic number?\n        body.set_shape(target_width, target_height)\n        body.move_to(content)\n        body.shift(self.bubble_center_adjustment_factor * body.get_height() * DOWN)\n        return body\n\n    def get_tip(self):\n        return self.get_corner(DOWN + self.direction)\n\n    def get_bubble_center(self):\n        factor = self.bubble_center_adjustment_factor\n        return self.get_center() + factor * self.get_height() * UP\n\n    def move_tip_to(self, point):\n        self.shift(point - self.get_tip())\n        return self\n\n    def flip(self, axis=UP, only_body=True, **kwargs):\n        super().flip(axis=axis, **kwargs)\n        if only_body:\n            # Flip in place, don't use kwargs\n            self.content.flip(axis=axis)\n        if abs(axis[1]) > 0:\n            self.direction = -np.array(self.direction)\n        return self\n\n    def pin_to(self, mobject, auto_flip=False):\n        mob_center = mobject.get_center()\n        want_to_flip = np.sign(mob_center[0]) != np.sign(self.direction[0])\n        if want_to_flip and auto_flip:\n            self.flip()\n        boundary_point = mobject.get_bounding_box_point(UP - self.direction)\n        vector_from_center = 1.0 * (boundary_point - mob_center)\n        self.move_tip_to(mob_center + vector_from_center)\n        return self\n\n    def position_mobject_inside(self, mobject, buff=MED_LARGE_BUFF):\n        mobject.set_max_width(self.body.get_width() - 2 * buff)\n        mobject.set_max_height(self.body.get_height() / 1.5 - 2 * buff)\n        mobject.shift(self.get_bubble_center() - mobject.get_center())\n        return mobject\n\n    def add_content(self, mobject):\n        self.position_mobject_inside(mobject)\n        self.content = mobject\n        return self.content\n\n    def write(self, text):\n        self.add_content(Text(text))\n        return self\n\n    def resize_to_content(self, buff=1.0):  # TODO\n        self.body.match_points(self.get_body(\n            self.content, self.direction, buff\n        ))\n\n    def clear(self):\n        self.remove(self.content)\n        return self\n\n\nclass SpeechBubble(Bubble):\n    def __init__(\n        self,\n        content: str | VMobject | None = None,\n        buff: float = MED_SMALL_BUFF,\n        filler_shape: Tuple[float, float] = (2.0, 1.0),\n        stem_height_to_bubble_height: float = 0.5,\n        stem_top_x_props: Tuple[float, float] = (0.2, 0.3),\n        **kwargs\n    ):\n        self.stem_height_to_bubble_height = stem_height_to_bubble_height\n        self.stem_top_x_props = stem_top_x_props\n        super().__init__(content, buff, filler_shape, **kwargs)\n\n    def get_body(self, content: VMobject, direction: Vect3, buff: float) -> VMobject:\n        rect = SurroundingRectangle(content, buff=buff)\n        rect.round_corners()\n        lp = rect.get_corner(DL)\n        rp = rect.get_corner(DR)\n        stem_height = self.stem_height_to_bubble_height * rect.get_height()\n        low_prop, high_prop = self.stem_top_x_props\n        triangle = Polygon(\n            interpolate(lp, rp, low_prop),\n            interpolate(lp, rp, high_prop),\n            lp + stem_height * DOWN,\n        )\n        result = Union(rect, triangle)\n        result.insert_n_curves(20)\n        if direction[0] > 0:\n            result.flip()\n\n        return result\n\n\nclass ThoughtBubble(Bubble):\n    def __init__(\n        self,\n        content: str | VMobject | None = None,\n        buff: float = SMALL_BUFF,\n        filler_shape: Tuple[float, float] = (2.0, 1.0),\n        bulge_radius: float = 0.35,\n        bulge_overlap: float = 0.25,\n        noise_factor: float = 0.1,\n        circle_radii: list[float] = [0.1, 0.15, 0.2],\n        **kwargs\n    ):\n        self.bulge_radius = bulge_radius\n        self.bulge_overlap = bulge_overlap\n        self.noise_factor = noise_factor\n        self.circle_radii = circle_radii\n        super().__init__(content, buff, filler_shape, **kwargs)\n\n    def get_body(self, content: VMobject, direction: Vect3, buff: float) -> VMobject:\n        rect = SurroundingRectangle(content, buff)\n        perimeter = rect.get_arc_length()\n        radius = self.bulge_radius\n        step = (1 - self.bulge_overlap) * (2 * radius)\n        nf = self.noise_factor\n        corners = [rect.get_corner(v) for v in [DL, UL, UR, DR]]\n        points = []\n        for c1, c2 in adjacent_pairs(corners):\n            n_alphas = int(get_norm(c1 - c2) / step) + 1\n            for alpha in np.linspace(0, 1, n_alphas):\n                points.append(interpolate(\n                    c1, c2, alpha + nf * (step / n_alphas) * (random.random() - 0.5)\n                ))\n\n        cloud = Union(rect, *(\n            # Add bulges\n            Circle(radius=radius * (1 + nf * random.random())).move_to(point)\n            for point in points\n        ))\n        cloud.set_stroke(WHITE, 2)\n\n        circles = VGroup(Circle(radius=radius) for radius in self.circle_radii)\n        circ_buff = 0.25 * self.circle_radii[0]\n        circles.arrange(UR, buff=circ_buff)\n        circles[1].shift(circ_buff * DR)\n        circles.next_to(cloud, DOWN, 4 * circ_buff, aligned_edge=LEFT)\n        circles.set_stroke(WHITE, 2)\n\n        result = VGroup(*circles, cloud)\n\n        if direction[0] > 0:\n            result.flip()\n\n        return result\n\n\nclass OldSpeechBubble(Bubble):\n    file_name: str = \"Bubbles_speech.svg\"\n\n\nclass DoubleSpeechBubble(Bubble):\n    file_name: str = \"Bubbles_double_speech.svg\"\n\n\nclass OldThoughtBubble(Bubble):\n    file_name: str = \"Bubbles_thought.svg\"\n\n    def get_body(self, content: VMobject, direction: Vect3, buff: float) -> VMobject:\n        body = super().get_body(content, direction, buff)\n        body.sort(lambda p: p[1])\n        return body\n\n    def make_green_screen(self):\n        self.body[-1].set_fill(GREEN_SCREEN, opacity=1)\n        return self\n\n\nclass VectorizedEarth(SVGMobject):\n    file_name: str = \"earth\"\n\n    def __init__(\n        self,\n        height: float = 2.0,\n        **kwargs\n    ):\n        super().__init__(height=height, **kwargs)\n        self.insert_n_curves(20)\n        circle = Circle(\n            stroke_width=3,\n            stroke_color=GREEN,\n            fill_opacity=1,\n            fill_color=BLUE_C,\n        )\n        circle.replace(self)\n        self.add_to_back(circle)\n\n\nclass Piano(VGroup):\n    def __init__(\n        self,\n        n_white_keys = 52,\n        black_pattern = [0, 2, 3, 5, 6],\n        white_keys_per_octave = 7,\n        white_key_dims = (0.15, 1.0),\n        black_key_dims = (0.1, 0.66),\n        key_buff = 0.02,\n        white_key_color = WHITE,\n        black_key_color = GREY_E,\n        total_width = 13,\n        **kwargs\n    ):\n        self.n_white_keys = n_white_keys\n        self.black_pattern = black_pattern\n        self.white_keys_per_octave = white_keys_per_octave\n        self.white_key_dims = white_key_dims\n        self.black_key_dims = black_key_dims\n        self.key_buff = key_buff\n        self.white_key_color = white_key_color\n        self.black_key_color = black_key_color\n        self.total_width = total_width\n\n        super().__init__(**kwargs)\n        self.add_white_keys()\n        self.add_black_keys()\n        self.sort_keys()\n        self[:-1].reverse_points()\n        self.set_width(self.total_width)\n\n    def add_white_keys(self):\n        key = Rectangle(*self.white_key_dims)\n        key.set_fill(self.white_key_color, 1)\n        key.set_stroke(width=0)\n        self.white_keys = key.get_grid(1, self.n_white_keys, buff=self.key_buff)\n        self.add(*self.white_keys)\n\n    def add_black_keys(self):\n        key = Rectangle(*self.black_key_dims)\n        key.set_fill(self.black_key_color, 1)\n        key.set_stroke(width=0)\n\n        self.black_keys = VGroup()\n        for i in range(len(self.white_keys) - 1):\n            if i % self.white_keys_per_octave not in self.black_pattern:\n                continue\n            wk1 = self.white_keys[i]\n            wk2 = self.white_keys[i + 1]\n            bk = key.copy()\n            bk.move_to(midpoint(wk1.get_top(), wk2.get_top()), UP)\n            big_bk = bk.copy()\n            big_bk.stretch((bk.get_width() + self.key_buff) / bk.get_width(), 0)\n            big_bk.stretch((bk.get_height() + self.key_buff) / bk.get_height(), 1)\n            big_bk.move_to(bk, UP)\n            for wk in wk1, wk2:\n                wk.become(Difference(wk, big_bk).match_style(wk))\n            self.black_keys.add(bk)\n        self.add(*self.black_keys)\n\n    def sort_keys(self):\n        self.sort(lambda p: p[0])\n\n\nclass Piano3D(VGroup):\n    def __init__(\n        self,\n        shading: Tuple[float, float, float] = (1.0, 0.2, 0.2),\n        stroke_width: float = 0.25,\n        stroke_color: ManimColor = BLACK,\n        key_depth: float = 0.1,\n        black_key_shift: float = 0.05,\n        piano_2d_config: dict = dict(\n            white_key_color=GREY_A,\n            key_buff=0.001\n        ),\n        **kwargs\n    ):\n        piano_2d = Piano(**piano_2d_config)\n        super().__init__(*(\n            Prismify(key, key_depth)\n            for key in piano_2d\n        ))\n        self.set_stroke(stroke_color, stroke_width)\n        self.set_shading(*shading)\n        self.apply_depth_test()\n\n        # Elevate black keys\n        for i, key in enumerate(self):\n            if piano_2d[i] in piano_2d.black_keys:\n                key.shift(black_key_shift * OUT)\n                key.set_color(BLACK)\n\n\nclass DieFace(VGroup):\n    def __init__(\n        self,\n        value: int,\n        side_length: float = 1.0,\n        corner_radius: float = 0.15,\n        stroke_color: ManimColor = WHITE,\n        stroke_width: float = 2.0,\n        fill_color: ManimColor = GREY_E,\n        dot_radius: float = 0.08,\n        dot_color: ManimColor = WHITE,\n        dot_coalesce_factor: float = 0.5\n    ):\n        dot = Dot(radius=dot_radius, fill_color=dot_color)\n        square = Square(\n            side_length=side_length,\n            stroke_color=stroke_color,\n            stroke_width=stroke_width,\n            fill_color=fill_color,\n            fill_opacity=1.0,\n        )\n        square.round_corners(corner_radius)\n\n        if not (1 <= value <= 6):\n            raise Exception(\"DieFace only accepts integer inputs between 1 and 6\")\n\n        edge_group = [\n            (ORIGIN,),\n            (UL, DR),\n            (UL, ORIGIN, DR),\n            (UL, UR, DL, DR),\n            (UL, UR, ORIGIN, DL, DR),\n            (UL, UR, LEFT, RIGHT, DL, DR),\n        ][value - 1]\n\n        arrangement = VGroup(*(\n            dot.copy().move_to(square.get_bounding_box_point(vect))\n            for vect in edge_group\n        ))\n        arrangement.space_out_submobjects(dot_coalesce_factor)\n\n        super().__init__(square, arrangement)\n        self.dots = arrangement\n        self.value = value\n        self.index = value\n\n\nclass Dartboard(VGroup):\n    radius = 3\n    n_sectors = 20\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        n_sectors = self.n_sectors\n        angle = TAU / n_sectors\n\n        segments = VGroup(*[\n            VGroup(*[\n                AnnularSector(\n                    inner_radius=in_r,\n                    outer_radius=out_r,\n                    start_angle=n * angle,\n                    angle=angle,\n                    fill_color=color,\n                )\n                for n, color in zip(\n                    range(n_sectors),\n                    it.cycle(colors)\n                )\n            ])\n            for colors, in_r, out_r in [\n                ([GREY_B, GREY_E], 0, 1),\n                ([GREEN_E, RED_E], 0.5, 0.55),\n                ([GREEN_E, RED_E], 0.95, 1),\n            ]\n        ])\n        segments.rotate(-angle / 2)\n        bullseyes = VGroup(*[\n            Circle(radius=r)\n            for r in [0.07, 0.035]\n        ])\n        bullseyes.set_fill(opacity=1)\n        bullseyes.set_stroke(width=0)\n        bullseyes[0].set_color(GREEN_E)\n        bullseyes[1].set_color(RED_E)\n\n        self.bullseye = bullseyes[1]\n        self.add(*segments, *bullseyes)\n        self.scale(self.radius)\n"
  },
  {
    "path": "manimlib/mobject/svg/old_tex_mobject.py",
    "content": "from __future__ import annotations\n\nfrom functools import reduce\nimport operator as op\nimport re\n\nfrom manimlib.constants import BLACK, DEFAULT_MOBJECT_COLOR\nfrom manimlib.mobject.svg.svg_mobject import SVGMobject\nfrom manimlib.mobject.svg.tex_mobject import get_tex_mob_scale_factor\nfrom manimlib.mobject.types.vectorized_mobject import VGroup\nfrom manimlib.utils.tex_file_writing import latex_to_svg\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Iterable, List, Dict\n    from manimlib.typing import ManimColor\n\n\nclass SingleStringTex(SVGMobject):\n    height: float | None = None\n\n    def __init__(\n        self,\n        tex_string: str,\n        height: float | None = None,\n        fill_color: ManimColor = DEFAULT_MOBJECT_COLOR,\n        fill_opacity: float = 1.0,\n        stroke_width: float = 0,\n        svg_default: dict = dict(fill_color=DEFAULT_MOBJECT_COLOR),\n        path_string_config: dict = dict(),\n        font_size: int = 48,\n        alignment: str = R\"\\centering\",\n        math_mode: bool = True,\n        organize_left_to_right: bool = False,\n        template: str = \"\",\n        additional_preamble: str = \"\",\n        **kwargs\n    ):\n        self.tex_string = tex_string\n        self.svg_default = dict(svg_default)\n        self.path_string_config = dict(path_string_config)\n        self.font_size = font_size\n        self.alignment = alignment\n        self.math_mode = math_mode\n        self.organize_left_to_right = organize_left_to_right\n        self.template = template\n        self.additional_preamble = additional_preamble\n\n        super().__init__(\n            height=height,\n            fill_color=fill_color,\n            fill_opacity=fill_opacity,\n            stroke_width=stroke_width,\n            path_string_config=path_string_config,\n            **kwargs\n        )\n\n        if self.height is None:\n            self.scale(get_tex_mob_scale_factor() * self.font_size)\n        if self.organize_left_to_right:\n            self.organize_submobjects_left_to_right()\n\n    @property\n    def hash_seed(self) -> tuple:\n        return (\n            self.__class__.__name__,\n            self.svg_default,\n            self.path_string_config,\n            self.tex_string,\n            self.alignment,\n            self.math_mode,\n            self.template,\n            self.additional_preamble\n        )\n\n    def get_svg_string_by_content(self, content: str) -> str:\n        return latex_to_svg(content, self.template, self.additional_preamble)\n\n    def get_tex_file_body(self, tex_string: str) -> str:\n        new_tex = self.get_modified_expression(tex_string)\n        if self.math_mode:\n            new_tex = \"\\\\begin{align*}\\n\" + new_tex + \"\\n\\\\end{align*}\"\n        return self.alignment + \"\\n\" + new_tex\n\n    def get_modified_expression(self, tex_string: str) -> str:\n        return self.modify_special_strings(tex_string.strip())\n\n    def modify_special_strings(self, tex: str) -> str:\n        tex = tex.strip()\n        should_add_filler = reduce(op.or_, [\n            # Fraction line needs something to be over\n            tex == \"\\\\over\",\n            tex == \"\\\\overline\",\n            # Makesure sqrt has overbar\n            tex == \"\\\\sqrt\",\n            tex == \"\\\\sqrt{\",\n            # Need to add blank subscript or superscript\n            tex.endswith(\"_\"),\n            tex.endswith(\"^\"),\n            tex.endswith(\"dot\"),\n        ])\n        if should_add_filler:\n            filler = \"{\\\\quad}\"\n            tex += filler\n\n        should_add_double_filler = reduce(op.or_, [\n            tex == \"\\\\overset\",\n            # TODO: these can't be used since they change\n            # the latex draw order.\n            # tex == \"\\\\frac\", # you can use \\\\over as a alternative \n            # tex == \"\\\\dfrac\",\n            # tex == \"\\\\binom\",\n        ])\n        if should_add_double_filler:\n            filler = \"{\\\\quad}{\\\\quad}\"\n            tex += filler\n\n        if tex == \"\\\\substack\":\n            tex = \"\\\\quad\"\n\n        if tex == \"\":\n            tex = \"\\\\quad\"\n\n        # To keep files from starting with a line break\n        if tex.startswith(\"\\\\\\\\\"):\n            tex = tex.replace(\"\\\\\\\\\", \"\\\\quad\\\\\\\\\")\n\n        tex = self.balance_braces(tex)\n\n        # Handle imbalanced \\left and \\right\n        num_lefts, num_rights = [\n            len([\n                s for s in tex.split(substr)[1:]\n                if s and s[0] in \"(){}[]|.\\\\\"\n            ])\n            for substr in (\"\\\\left\", \"\\\\right\")\n        ]\n        if num_lefts != num_rights:\n            tex = tex.replace(\"\\\\left\", \"\\\\big\")\n            tex = tex.replace(\"\\\\right\", \"\\\\big\")\n\n        for context in [\"array\"]:\n            begin_in = (\"\\\\begin{%s}\" % context) in tex\n            end_in = (\"\\\\end{%s}\" % context) in tex\n            if begin_in ^ end_in:\n                # Just turn this into a blank string,\n                # which means caller should leave a\n                # stray \\\\begin{...} with other symbols\n                tex = \"\"\n        return tex\n\n    def balance_braces(self, tex: str) -> str:\n        \"\"\"\n        Makes Tex resiliant to unmatched braces\n        \"\"\"\n        num_unclosed_brackets = 0\n        for i in range(len(tex)):\n            if i > 0 and tex[i - 1] == \"\\\\\":\n                # So as to not count '\\{' type expressions\n                continue\n            char = tex[i]\n            if char == \"{\":\n                num_unclosed_brackets += 1\n            elif char == \"}\":\n                if num_unclosed_brackets == 0:\n                    tex = \"{\" + tex\n                else:\n                    num_unclosed_brackets -= 1\n        tex += num_unclosed_brackets * \"}\"\n        return tex\n\n    def get_tex(self) -> str:\n        return self.tex_string\n\n    def organize_submobjects_left_to_right(self):\n        self.sort(lambda p: p[0])\n        return self\n\n\nclass OldTex(SingleStringTex):\n    def __init__(\n        self,\n        *tex_strings: str,\n        arg_separator: str = \"\",\n        isolate: List[str] = [],\n        tex_to_color_map: Dict[str, ManimColor] = {},\n        **kwargs\n    ):\n        self.tex_strings = self.break_up_tex_strings(\n            tex_strings,\n            substrings_to_isolate=[*isolate, *tex_to_color_map.keys()]\n        )\n        full_string = arg_separator.join(self.tex_strings)\n\n        super().__init__(full_string, **kwargs)\n        self.break_up_by_substrings(self.tex_strings)\n        self.set_color_by_tex_to_color_map(tex_to_color_map)\n\n        if self.organize_left_to_right:\n            self.organize_submobjects_left_to_right()\n\n    def break_up_tex_strings(self, tex_strings: Iterable[str], substrings_to_isolate: List[str] = []) -> Iterable[str]:\n        # Separate out any strings specified in the isolate\n        # or tex_to_color_map lists.\n        if len(substrings_to_isolate) == 0:\n            return tex_strings\n        patterns = (\n            \"({})\".format(re.escape(ss))\n            for ss in substrings_to_isolate\n        )\n        pattern = \"|\".join(patterns)\n        pieces = []\n        for s in tex_strings:\n            if pattern:\n                pieces.extend(re.split(pattern, s))\n            else:\n                pieces.append(s)\n        return list(filter(lambda s: s, pieces))\n\n    def break_up_by_substrings(self, tex_strings: Iterable[str]):\n        \"\"\"\n        Reorganize existing submojects one layer\n        deeper based on the structure of tex_strings (as a list\n        of tex_strings)\n        \"\"\"\n        if len(list(tex_strings)) == 1:\n            submob = self.copy()\n            self.set_submobjects([submob])\n            return self\n        new_submobjects = []\n        curr_index = 0\n        for tex_string in tex_strings:\n            tex_string = tex_string.strip()\n            if len(tex_string) == 0:\n                continue\n            sub_tex_mob = SingleStringTex(tex_string, math_mode=self.math_mode)\n            num_submobs = len(sub_tex_mob)\n            if num_submobs == 0:\n                continue\n            new_index = curr_index + num_submobs\n            sub_tex_mob.set_submobjects(self.submobjects[curr_index:new_index])\n            new_submobjects.append(sub_tex_mob)\n            curr_index = new_index\n        self.set_submobjects(new_submobjects)\n        return self\n\n    def get_parts_by_tex(\n        self,\n        tex: str,\n        substring: bool = True,\n        case_sensitive: bool = True\n    ) -> VGroup:\n        def test(tex1, tex2):\n            if not case_sensitive:\n                tex1 = tex1.lower()\n                tex2 = tex2.lower()\n            if substring:\n                return tex1 in tex2\n            else:\n                return tex1 == tex2\n\n        return VGroup(*filter(\n            lambda m: isinstance(m, SingleStringTex) and test(tex, m.get_tex()),\n            self.submobjects\n        ))\n\n    def get_part_by_tex(self, tex: str, **kwargs) -> SingleStringTex | None:\n        all_parts = self.get_parts_by_tex(tex, **kwargs)\n        return all_parts[0] if all_parts else None\n\n    def set_color_by_tex(self, tex: str, color: ManimColor, **kwargs):\n        self.get_parts_by_tex(tex, **kwargs).set_color(color)\n        return self\n\n    def set_color_by_tex_to_color_map(\n        self,\n        tex_to_color_map: dict[str, ManimColor],\n        **kwargs\n    ):\n        for tex, color in list(tex_to_color_map.items()):\n            self.set_color_by_tex(tex, color, **kwargs)\n        return self\n\n    def index_of_part(self, part: SingleStringTex, start: int = 0) -> int:\n        return self.submobjects.index(part, start)\n\n    def index_of_part_by_tex(self, tex: str, start: int = 0, **kwargs) -> int:\n        part = self.get_part_by_tex(tex, **kwargs)\n        return self.index_of_part(part, start)\n\n    def slice_by_tex(\n        self,\n        start_tex: str | None = None,\n        stop_tex: str | None = None,\n        **kwargs\n    ) -> VGroup:\n        if start_tex is None:\n            start_index = 0\n        else:\n            start_index = self.index_of_part_by_tex(start_tex, **kwargs)\n\n        if stop_tex is None:\n            return self[start_index:]\n        else:\n            stop_index = self.index_of_part_by_tex(stop_tex, start=start_index, **kwargs)\n            return self[start_index:stop_index]\n\n    def sort_alphabetically(self) -> None:\n        self.submobjects.sort(key=lambda m: m.get_tex())\n\n    def set_bstroke(self, color: ManimColor = BLACK, width: float = 4):\n        self.set_stroke(color, width, background=True)\n        return self\n\n\nclass OldTexText(OldTex):\n    def __init__(\n        self,\n        *tex_strings: str,\n        math_mode: bool = False,\n        arg_separator: str = \"\",\n        **kwargs\n    ):\n        super().__init__(\n            *tex_strings,\n            math_mode=math_mode,\n            arg_separator=arg_separator,\n            **kwargs\n        )\n"
  },
  {
    "path": "manimlib/mobject/svg/special_tex.py",
    "content": "from __future__ import annotations\n\nfrom manimlib.constants import MED_SMALL_BUFF, DEFAULT_MOBJECT_COLOR, GREY_C\nfrom manimlib.constants import DOWN, LEFT, RIGHT, UP\nfrom manimlib.constants import FRAME_WIDTH\nfrom manimlib.constants import MED_LARGE_BUFF, SMALL_BUFF\nfrom manimlib.mobject.geometry import Line\nfrom manimlib.mobject.types.vectorized_mobject import VGroup\nfrom manimlib.mobject.svg.tex_mobject import TexText\n\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from manimlib.typing import ManimColor, Vect3\n\n\nclass BulletedList(VGroup):\n    def __init__(\n        self,\n        *items: str,\n        buff: float = MED_LARGE_BUFF,\n        aligned_edge: Vect3 = LEFT,\n        numbered: bool = False,\n        **kwargs\n    ):\n        labelled_content = [R\"\\item \" + item for item in items]\n        enum_str = \"enumerate\" if numbered else \"itemize\"\n        tex_string = \"\\n\".join([\n            fR\"\\begin{{{enum_str}}}\",\n            *labelled_content,\n            fR\"\\end{{{enum_str}}}\"\n        ])\n        tex_text = TexText(tex_string, isolate=labelled_content, **kwargs)\n        lines = (tex_text.select_part(part) for part in labelled_content)\n\n        super().__init__(*lines)\n\n        self.arrange(DOWN, buff=buff, aligned_edge=aligned_edge)\n\n    def fade_all_but(self, index: int, opacity: float = 0.25, scale_factor=0.7) -> None:\n        max_dot_height = max([item[0].get_height() for item in self.submobjects])\n        for i, part in enumerate(self.submobjects):\n            trg_dot_height = (1.0 if i == index else scale_factor) * max_dot_height\n            part.set_fill(opacity=(1.0 if i == index else opacity))\n            part.scale(trg_dot_height / part[0].get_height(), about_edge=LEFT)\n\n\nclass TexTextFromPresetString(TexText):\n    tex: str = \"\"\n    default_color: ManimColor = DEFAULT_MOBJECT_COLOR\n\n    def __init__(self, **kwargs):\n        super().__init__(\n            self.tex,\n            color=kwargs.pop(\"color\", self.default_color),\n            **kwargs\n        )\n\n\nclass Title(TexText):\n    def __init__(\n        self,\n        *text_parts: str,\n        font_size: int = 72,\n        include_underline: bool = True,\n        underline_width: float = FRAME_WIDTH - 2,\n        # This will override underline_width\n        match_underline_width_to_text: bool = False,\n        underline_buff: float = SMALL_BUFF,\n        underline_style: dict = dict(stroke_width=2, stroke_color=GREY_C),\n        **kwargs\n    ):\n        super().__init__(*text_parts, font_size=font_size, **kwargs)\n        self.to_edge(UP, buff=MED_SMALL_BUFF)\n        if include_underline:\n            underline = Line(LEFT, RIGHT, **underline_style)\n            underline.next_to(self, DOWN, buff=underline_buff)\n            if match_underline_width_to_text:\n                underline.match_width(self)\n            else:\n                underline.set_width(underline_width)\n            self.add(underline)\n            self.underline = underline\n"
  },
  {
    "path": "manimlib/mobject/svg/string_mobject.py",
    "content": "from __future__ import annotations\n\nfrom abc import ABC, abstractmethod\nimport itertools as it\nimport re\nfrom scipy.optimize import linear_sum_assignment\nfrom scipy.spatial.distance import cdist\n\nfrom manimlib.constants import DEFAULT_MOBJECT_COLOR\nfrom manimlib.logger import log\nfrom manimlib.mobject.svg.svg_mobject import SVGMobject\nfrom manimlib.mobject.types.vectorized_mobject import VMobject\nfrom manimlib.mobject.types.vectorized_mobject import VGroup\nfrom manimlib.utils.color import color_to_hex\nfrom manimlib.utils.color import hex_to_int\nfrom manimlib.utils.color import int_to_hex\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable\n    from manimlib.typing import ManimColor, Span, Selector\n\n\nclass StringMobject(SVGMobject, ABC):\n    \"\"\"\n    An abstract base class for `Tex` and `MarkupText`\n\n    This class aims to optimize the logic of \"slicing submobjects\n    via substrings\". This could be much clearer and more user-friendly\n    than slicing through numerical indices explicitly.\n\n    Users are expected to specify substrings in `isolate` parameter\n    if they want to do anything with their corresponding submobjects.\n    `isolate` parameter can be either a string, a `re.Pattern` object,\n    or a 2-tuple containing integers or None, or a collection of the above.\n    Note, substrings specified cannot *partly* overlap with each other.\n\n    Each instance of `StringMobject` may generate 2 svg files.\n    The additional one is generated with some color commands inserted,\n    so that each submobject of the original `SVGMobject` will be labelled\n    by the color of its paired submobject from the additional `SVGMobject`.\n    \"\"\"\n    height = None\n\n    def __init__(\n        self,\n        string: str,\n        fill_color: ManimColor = DEFAULT_MOBJECT_COLOR,\n        fill_border_width: float = 0.5,\n        stroke_color: ManimColor = DEFAULT_MOBJECT_COLOR,\n        stroke_width: float = 0,\n        base_color: ManimColor = DEFAULT_MOBJECT_COLOR,\n        isolate: Selector = (),\n        protect: Selector = (),\n        # When set to true, only the labelled svg is\n        # rendered, and its contents are used directly\n        # for the body of this String Mobject\n        use_labelled_svg: bool = False,\n        **kwargs\n    ):\n        self.string = string\n        self.base_color = base_color or DEFAULT_MOBJECT_COLOR\n        self.isolate = isolate\n        self.protect = protect\n        self.use_labelled_svg = use_labelled_svg\n\n        self.parse()\n        svg_string = self.get_svg_string()\n        super().__init__(svg_string=svg_string, **kwargs)\n        self.set_stroke(stroke_color, stroke_width)\n        self.set_fill(fill_color, border_width=fill_border_width)\n        self.labels = [submob.label for submob in self.submobjects]\n\n    def get_svg_string(self, is_labelled: bool = False) -> str:\n        content = self.get_content(is_labelled or self.use_labelled_svg)\n        return self.get_svg_string_by_content(content)\n\n    @abstractmethod\n    def get_svg_string_by_content(self, content: str) -> str:\n        return \"\"\n\n    def assign_labels_by_color(self, mobjects: list[VMobject]) -> None:\n        \"\"\"\n        Assuming each mobject in the list `mobjects` has a fill color\n        meant to represent a numerical label, this assigns those\n        those numerical labels to each mobject as an attribute\n        \"\"\"\n        labels_count = len(self.labelled_spans)\n        if labels_count == 1:\n            for mob in mobjects:\n                mob.label = 0\n            return\n\n        unrecognizable_colors = []\n        for mob in mobjects:\n            label = hex_to_int(color_to_hex(mob.get_fill_color()))\n            if label >= labels_count:\n                unrecognizable_colors.append(label)\n                label = 0\n            mob.label = label\n\n        if unrecognizable_colors:\n            log.warning(\n                \"Unrecognizable color labels detected (%s). \" + \\\n                \"The result could be unexpected.\",\n                \", \".join(\n                    int_to_hex(color)\n                    for color in unrecognizable_colors\n                )\n            )\n\n    def mobjects_from_svg_string(self, svg_string: str) -> list[VMobject]:\n        submobs = super().mobjects_from_svg_string(svg_string)\n\n        if self.use_labelled_svg:\n            # This means submobjects are colored according to spans\n            self.assign_labels_by_color(submobs)\n            return submobs\n\n        # Otherwise, submobs are not colored, so generate a new list\n        # of submobject which are and use those for labels\n        unlabelled_submobs = submobs\n        labelled_content = self.get_content(is_labelled=True)\n        labelled_file = self.get_svg_string_by_content(labelled_content)\n        labelled_submobs = super().mobjects_from_svg_string(labelled_file)\n        self.labelled_submobs = labelled_submobs\n        self.unlabelled_submobs = unlabelled_submobs\n\n        self.assign_labels_by_color(labelled_submobs)\n        self.rearrange_submobjects_by_positions(labelled_submobs, unlabelled_submobs)\n        for usm, lsm in zip(unlabelled_submobs, labelled_submobs):\n            usm.label = lsm.label\n\n        if len(unlabelled_submobs) != len(labelled_submobs):\n            log.warning(\n                \"Cannot align submobjects of the labelled svg \" + \\\n                \"to the original svg. Skip the labelling process.\"\n            )\n            for usm in unlabelled_submobs:\n                usm.label = 0\n            return unlabelled_submobs\n\n        return unlabelled_submobs\n\n    def rearrange_submobjects_by_positions(\n        self, labelled_submobs: list[VMobject], unlabelled_submobs: list[VMobject],\n    ) -> None:\n        \"\"\"\n        Rearrange `labeleled_submobjects` so that each submobject\n        is labelled by the nearest one of `unlabelled_submobs`.\n        The correctness cannot be ensured, since the svg may\n        change significantly after inserting color commands.\n        \"\"\"\n        if len(labelled_submobs) == 0:\n            return\n\n        labelled_svg = VGroup(*labelled_submobs)\n        labelled_svg.replace(VGroup(*unlabelled_submobs))\n        distance_matrix = cdist(\n            [submob.get_center() for submob in unlabelled_submobs],\n            [submob.get_center() for submob in labelled_submobs]\n        )\n        _, indices = linear_sum_assignment(distance_matrix)\n        labelled_submobs[:] = [labelled_submobs[index] for index in indices]\n\n    # Toolkits\n\n    def find_spans_by_selector(self, selector: Selector) -> list[Span]:\n        def find_spans_by_single_selector(sel):\n            if isinstance(sel, str):\n                return [\n                    match_obj.span()\n                    for match_obj in re.finditer(re.escape(sel), self.string)\n                ]\n            if isinstance(sel, re.Pattern):\n                return [\n                    match_obj.span()\n                    for match_obj in sel.finditer(self.string)\n                ]\n            if isinstance(sel, tuple) and len(sel) == 2 and all(\n                isinstance(index, int) or index is None\n                for index in sel\n            ):\n                l = len(self.string)\n                span = tuple(\n                    default_index if index is None else\n                    min(index, l) if index >= 0 else max(index + l, 0)\n                    for index, default_index in zip(sel, (0, l))\n                )\n                return [span]\n            return None\n\n        result = find_spans_by_single_selector(selector)\n        if result is None:\n            result = []\n            for sel in selector:\n                spans = find_spans_by_single_selector(sel)\n                if spans is None:\n                    raise TypeError(f\"Invalid selector: '{sel}'\")\n                result.extend(spans)\n        return list(filter(lambda span: span[0] <= span[1], result))\n\n    @staticmethod\n    def span_contains(span_0: Span, span_1: Span) -> bool:\n        return span_0[0] <= span_1[0] and span_0[1] >= span_1[1]\n\n    # Parsing\n\n    def parse(self) -> None:\n        def get_substr(span: Span) -> str:\n            return self.string[slice(*span)]\n\n        configured_items = self.get_configured_items()\n        isolated_spans = self.find_spans_by_selector(self.isolate)\n        protected_spans = self.find_spans_by_selector(self.protect)\n        command_matches = self.get_command_matches(self.string)\n\n        def get_key(category, i, flag):\n            def get_span_by_category(category, i):\n                if category == 0:\n                    return configured_items[i][0]\n                if category == 1:\n                    return isolated_spans[i]\n                if category == 2:\n                    return protected_spans[i]\n                return command_matches[i].span()\n\n            index, paired_index = get_span_by_category(category, i)[::flag]\n            return (\n                index,\n                flag * (2 if index != paired_index else -1),\n                -paired_index,\n                flag * category,\n                flag * i\n            )\n\n        index_items = sorted([\n            (category, i, flag)\n            for category, item_length in enumerate((\n                len(configured_items),\n                len(isolated_spans),\n                len(protected_spans),\n                len(command_matches)\n            ))\n            for i in range(item_length)\n            for flag in (1, -1)\n        ], key=lambda t: get_key(*t))\n\n        inserted_items = []\n        labelled_items = []\n        overlapping_spans = []\n        level_mismatched_spans = []\n\n        label = 1\n        protect_level = 0\n        bracket_stack = [0]\n        bracket_count = 0\n        open_command_stack = []\n        open_stack = []\n        for category, i, flag in index_items:\n            if category >= 2:\n                protect_level += flag\n                if flag == 1 or category == 2:\n                    continue\n                inserted_items.append((i, 0))\n                command_match = command_matches[i]\n                command_flag = self.get_command_flag(command_match)\n                if command_flag == 1:\n                    bracket_count += 1\n                    bracket_stack.append(bracket_count)\n                    open_command_stack.append((len(inserted_items), i))\n                    continue\n                if command_flag == 0:\n                    continue\n                pos, i_ = open_command_stack.pop()\n                bracket_stack.pop()\n                open_command_match = command_matches[i_]\n                attr_dict = self.get_attr_dict_from_command_pair(\n                    open_command_match, command_match\n                )\n                if attr_dict is None:\n                    continue\n                span = (open_command_match.end(), command_match.start())\n                labelled_items.append((span, attr_dict))\n                inserted_items.insert(pos, (label, 1))\n                inserted_items.insert(-1, (label, -1))\n                label += 1\n                continue\n            if flag == 1:\n                open_stack.append((\n                    len(inserted_items), category, i,\n                    protect_level, bracket_stack.copy()\n                ))\n                continue\n            span, attr_dict = configured_items[i] \\\n                if category == 0 else (isolated_spans[i], {})\n            pos, category_, i_, protect_level_, bracket_stack_ \\\n                = open_stack.pop()\n            if category_ != category or i_ != i:\n                overlapping_spans.append(span)\n                continue\n            if protect_level_ or protect_level:\n                continue\n            if bracket_stack_ != bracket_stack:\n                level_mismatched_spans.append(span)\n                continue\n            labelled_items.append((span, attr_dict))\n            inserted_items.insert(pos, (label, 1))\n            inserted_items.append((label, -1))\n            label += 1\n        labelled_items.insert(0, ((0, len(self.string)), {}))\n        inserted_items.insert(0, (0, 1))\n        inserted_items.append((0, -1))\n\n        if overlapping_spans:\n            log.warning(\n                \"Partly overlapping substrings detected: %s\",\n                \", \".join(\n                    f\"'{get_substr(span)}'\"\n                    for span in overlapping_spans\n                )\n            )\n        if level_mismatched_spans:\n            log.warning(\n                \"Cannot handle substrings: %s\",\n                \", \".join(\n                    f\"'{get_substr(span)}'\"\n                    for span in level_mismatched_spans\n                )\n            )\n\n        def reconstruct_string(\n            start_item: tuple[int, int],\n            end_item: tuple[int, int],\n            command_replace_func: Callable[[re.Match], str],\n            command_insert_func: Callable[[int, int, dict[str, str]], str]\n        ) -> str:\n            def get_edge_item(i: int, flag: int) -> tuple[Span, str]:\n                if flag == 0:\n                    match_obj = command_matches[i]\n                    return (\n                        match_obj.span(),\n                        command_replace_func(match_obj)\n                    )\n                span, attr_dict = labelled_items[i]\n                index = span[flag < 0]\n                return (\n                    (index, index),\n                    command_insert_func(i, flag, attr_dict)\n                )\n\n            items = [\n                get_edge_item(i, flag)\n                for i, flag in inserted_items[slice(\n                    inserted_items.index(start_item),\n                    inserted_items.index(end_item) + 1\n                )]\n            ]\n            pieces = [\n                get_substr((start, end))\n                for start, end in zip(\n                    [interval_end for (_, interval_end), _ in items[:-1]],\n                    [interval_start for (interval_start, _), _ in items[1:]]\n                )\n            ]\n            interval_pieces = [piece for _, piece in items[1:-1]]\n            return \"\".join(it.chain(*zip(pieces, (*interval_pieces, \"\"))))\n\n        self.labelled_spans = [span for span, _ in labelled_items]\n        self.reconstruct_string = reconstruct_string\n\n    def get_content(self, is_labelled: bool) -> str:\n        content = self.reconstruct_string(\n            (0, 1), (0, -1),\n            self.replace_for_content,\n            lambda label, flag, attr_dict: self.get_command_string(\n                attr_dict,\n                is_end=flag < 0,\n                label_hex=int_to_hex(label) if is_labelled else None\n            )\n        )\n        prefix, suffix = self.get_content_prefix_and_suffix(\n            is_labelled=is_labelled\n        )\n        return \"\".join((prefix, content, suffix))\n\n    @staticmethod\n    @abstractmethod\n    def get_command_matches(string: str) -> list[re.Match]:\n        return []\n\n    @staticmethod\n    @abstractmethod\n    def get_command_flag(match_obj: re.Match) -> int:\n        return 0\n\n    @staticmethod\n    @abstractmethod\n    def replace_for_content(match_obj: re.Match) -> str:\n        return \"\"\n\n    @staticmethod\n    @abstractmethod\n    def replace_for_matching(match_obj: re.Match) -> str:\n        return \"\"\n\n    @staticmethod\n    @abstractmethod\n    def get_attr_dict_from_command_pair(\n        open_command: re.Match, close_command: re.Match,\n    ) -> dict[str, str] | None:\n        return None\n\n    @abstractmethod\n    def get_configured_items(self) -> list[tuple[Span, dict[str, str]]]:\n        return []\n\n    @staticmethod\n    @abstractmethod\n    def get_command_string(\n        attr_dict: dict[str, str], is_end: bool, label_hex: str | None\n    ) -> str:\n        return \"\"\n\n    @abstractmethod\n    def get_content_prefix_and_suffix(\n        self, is_labelled: bool\n    ) -> tuple[str, str]:\n        return \"\", \"\"\n\n    # Selector\n\n    def get_submob_indices_list_by_span(\n        self, arbitrary_span: Span\n    ) -> list[int]:\n        return [\n            submob_index\n            for submob_index, label in enumerate(self.labels)\n            if self.span_contains(arbitrary_span, self.labelled_spans[label])\n        ]\n\n    def get_specified_part_items(self) -> list[tuple[str, list[int]]]:\n        return [\n            (\n                self.string[slice(*span)],\n                self.get_submob_indices_list_by_span(span)\n            )\n            for span in self.labelled_spans[1:]\n        ]\n\n    def get_specified_substrings(self) -> list[str]:\n        substrs = [\n            self.string[slice(*span)]\n            for span in self.labelled_spans[1:]\n        ]\n        # Use dict.fromkeys to remove duplicates while retaining order\n        return list(dict.fromkeys(substrs).keys())\n\n    def get_group_part_items(self) -> list[tuple[str, list[int]]]:\n        if not self.labels:\n            return []\n\n        def get_neighbouring_pairs(vals):\n            return list(zip(vals[:-1], vals[1:]))\n\n        range_lens, group_labels = zip(*(\n            (len(list(grouper)), val)\n            for val, grouper in it.groupby(self.labels)\n        ))\n        submob_indices_lists = [\n            list(range(*submob_range))\n            for submob_range in get_neighbouring_pairs(\n                [0, *it.accumulate(range_lens)]\n            )\n        ]\n        labelled_spans = self.labelled_spans\n        start_items = [\n            (group_labels[0], 1),\n            *(\n                (curr_label, 1)\n                if self.span_contains(\n                    labelled_spans[prev_label], labelled_spans[curr_label]\n                )\n                else (prev_label, -1)\n                for prev_label, curr_label in get_neighbouring_pairs(\n                    group_labels\n                )\n            )\n        ]\n        end_items = [\n            *(\n                (curr_label, -1)\n                if self.span_contains(\n                    labelled_spans[next_label], labelled_spans[curr_label]\n                )\n                else (next_label, 1)\n                for curr_label, next_label in get_neighbouring_pairs(\n                    group_labels\n                )\n            ),\n            (group_labels[-1], -1)\n        ]\n        group_substrs = [\n            re.sub(r\"\\s+\", \"\", self.reconstruct_string(\n                start_item, end_item,\n                self.replace_for_matching,\n                lambda label, flag, attr_dict: \"\"\n            ))\n            for start_item, end_item in zip(start_items, end_items)\n        ]\n        return list(zip(group_substrs, submob_indices_lists))\n\n    def get_submob_indices_lists_by_selector(\n        self, selector: Selector\n    ) -> list[list[int]]:\n        return list(filter(\n            lambda indices_list: indices_list,\n            [\n                self.get_submob_indices_list_by_span(span)\n                for span in self.find_spans_by_selector(selector)\n            ]\n        ))\n\n    def build_parts_from_indices_lists(\n        self, indices_lists: list[list[int]]\n    ) -> VGroup:\n        return VGroup(*(\n            VGroup(*(\n                self.submobjects[submob_index]\n                for submob_index in indices_list\n            ))\n            for indices_list in indices_lists\n        ))\n\n    def build_groups(self) -> VGroup:\n        return self.build_parts_from_indices_lists([\n            indices_list\n            for _, indices_list in self.get_group_part_items()\n        ])\n\n    def select_parts(self, selector: Selector) -> VGroup:\n        specified_substrings = self.get_specified_substrings()\n        if isinstance(selector, (str, re.Pattern)) and selector not in specified_substrings:\n            return self.select_unisolated_substring(selector)\n        indices_list = self.get_submob_indices_lists_by_selector(selector)\n        return self.build_parts_from_indices_lists(indices_list)\n\n    def __getitem__(self, value: int | slice | Selector) -> VMobject:\n        if isinstance(value, (int, slice)):\n            return super().__getitem__(value)\n        return self.select_parts(value)\n\n    def select_part(self, selector: Selector, index: int = 0) -> VMobject:\n        return self.select_parts(selector)[index]\n\n    def substr_to_path_count(self, substr: str) -> int:\n        return len(re.sub(r\"\\s\", \"\", substr))\n\n    def get_symbol_substrings(self):\n        return list(re.sub(r\"\\s\", \"\", self.string))\n\n    def select_unisolated_substring(self, pattern: str | re.Pattern) -> VGroup:\n        if isinstance(pattern, str):\n            pattern = re.compile(re.escape(pattern))\n        result = []\n        for match in re.finditer(pattern, self.string):\n            index = match.start()\n            start = self.substr_to_path_count(self.string[:index])\n            substr = match.group()\n            end = start + self.substr_to_path_count(substr)\n            result.append(self[start:end])\n        return VGroup(*result)\n\n    def set_parts_color(self, selector: Selector, color: ManimColor):\n        self.select_parts(selector).set_color(color)\n        return self\n\n    def set_parts_color_by_dict(self, color_map: dict[Selector, ManimColor]):\n        for selector, color in color_map.items():\n            self.set_parts_color(selector, color)\n        return self\n\n    def get_string(self) -> str:\n        return self.string\n"
  },
  {
    "path": "manimlib/mobject/svg/svg_mobject.py",
    "content": "from __future__ import annotations\n\nfrom xml.etree import ElementTree as ET\n\nimport numpy as np\nimport svgelements as se\nimport io\nfrom pathlib import Path\n\nfrom manimlib.constants import RIGHT\nfrom manimlib.constants import TAU\nfrom manimlib.logger import log\nfrom manimlib.mobject.geometry import Circle\nfrom manimlib.mobject.geometry import Line\nfrom manimlib.mobject.geometry import Polygon\nfrom manimlib.mobject.geometry import Polyline\nfrom manimlib.mobject.geometry import Rectangle\nfrom manimlib.mobject.geometry import RoundedRectangle\nfrom manimlib.mobject.types.vectorized_mobject import VMobject\nfrom manimlib.utils.bezier import quadratic_bezier_points_for_arc\nfrom manimlib.utils.images import get_full_vector_image_path\nfrom manimlib.utils.iterables import hash_obj\nfrom manimlib.utils.space_ops import rotation_about_z\n\nfrom typing import TYPE_CHECKING\nif TYPE_CHECKING:\n    from manimlib.typing import ManimColor, Vect3Array\n\n\nSVG_HASH_TO_MOB_MAP: dict[int, list[VMobject]] = {}\nPATH_TO_POINTS: dict[str, Vect3Array] = {}\n\n\ndef get_svg_content_height(svg_string: str) -> float:\n    # Strip root attributes to match SVGMobject.modify_xml_tree,\n    # which avoids viewBox unit conversions (e.g. pt to px for dvisvgm)\n    root = ET.fromstring(svg_string)\n    root.attrib.clear()\n    data_stream = io.BytesIO()\n    ET.ElementTree(root).write(data_stream)\n    data_stream.seek(0)\n    svg = se.SVG.parse(data_stream)\n    bbox = svg.bbox()\n    if bbox is None:\n        raise ValueError(\"SVG has no content to measure\")\n    return bbox[3] - bbox[1]\n\n\ndef _convert_point_to_3d(x: float, y: float) -> np.ndarray:\n    return np.array([x, y, 0.0])\n\n\nclass SVGMobject(VMobject):\n    file_name: str = \"\"\n    height: float | None = 2.0\n    width: float | None = None\n\n    def __init__(\n        self,\n        file_name: str = \"\",\n        svg_string: str = \"\",\n        should_center: bool = True,\n        height: float | None = None,\n        width: float | None = None,\n        # Style that overrides the original svg\n        color: ManimColor = None,\n        fill_color: ManimColor = None,\n        fill_opacity: float | None = None,\n        stroke_width: float | None = 0.0,\n        stroke_color: ManimColor = None,\n        stroke_opacity: float | None = None,\n        # Style that fills only when not specified\n        # If None, regarded as default values from svg standard\n        svg_default: dict = dict(\n            color=None,\n            opacity=None,\n            fill_color=None,\n            fill_opacity=None,\n            stroke_width=None,\n            stroke_color=None,\n            stroke_opacity=None,\n        ),\n        path_string_config: dict = dict(),\n        **kwargs\n    ):\n        if svg_string != \"\":\n            self.svg_string = svg_string\n        elif file_name != \"\":\n            self.svg_string = self.file_name_to_svg_string(file_name)\n        elif self.file_name != \"\":\n            self.svg_string = self.file_name_to_svg_string(self.file_name)\n        else:\n            raise Exception(\"Must specify either a file_name or svg_string SVGMobject\")\n\n        self.svg_default = dict(svg_default)\n        self.path_string_config = dict(path_string_config)\n\n        super().__init__(**kwargs)\n        self.init_svg_mobject()\n        self.ensure_positive_orientation()\n\n        # Rather than passing style into super().__init__\n        # do it after svg has been taken in\n        self.set_style(\n            fill_color=color or fill_color,\n            fill_opacity=fill_opacity,\n            stroke_color=color or stroke_color,\n            stroke_width=stroke_width,\n            stroke_opacity=stroke_opacity,\n        )\n\n        # Initialize position\n        height = height or self.height\n        width = width or self.width\n\n        if should_center:\n            self.center()\n        if height is not None:\n            self.set_height(height)\n        if width is not None:\n            self.set_width(width)\n\n    def init_svg_mobject(self) -> None:\n        hash_val = hash_obj(self.hash_seed)\n        if hash_val in SVG_HASH_TO_MOB_MAP:\n            submobs = [sm.copy() for sm in SVG_HASH_TO_MOB_MAP[hash_val]]\n        else:\n            submobs = self.mobjects_from_svg_string(self.svg_string)\n            SVG_HASH_TO_MOB_MAP[hash_val] = [sm.copy() for sm in submobs]\n\n        self.add(*submobs)\n        self.flip(RIGHT)  # Flip y\n\n    @property\n    def hash_seed(self) -> tuple:\n        # Returns data which can uniquely represent the result of `init_points`.\n        # The hashed value of it is stored as a key in `SVG_HASH_TO_MOB_MAP`.\n        return (\n            self.__class__.__name__,\n            self.svg_default,\n            self.path_string_config,\n            self.svg_string\n        )\n\n    def mobjects_from_svg_string(self, svg_string: str) -> list[VMobject]:\n        element_tree = ET.ElementTree(ET.fromstring(svg_string))\n        new_tree = self.modify_xml_tree(element_tree)\n\n        # New svg based on tree contents\n        data_stream = io.BytesIO()\n        new_tree.write(data_stream)\n        data_stream.seek(0)\n        svg = se.SVG.parse(data_stream)\n        data_stream.close()\n\n        return self.mobjects_from_svg(svg)\n\n    def file_name_to_svg_string(self, file_name: str) -> str:\n        return Path(get_full_vector_image_path(file_name)).read_text()\n\n    def modify_xml_tree(self, element_tree: ET.ElementTree) -> ET.ElementTree:\n        config_style_attrs = self.generate_config_style_dict()\n        style_keys = (\n            \"fill\",\n            \"fill-opacity\",\n            \"stroke\",\n            \"stroke-opacity\",\n            \"stroke-width\",\n            \"style\"\n        )\n        root = element_tree.getroot()\n        style_attrs = {\n            k: v\n            for k, v in root.attrib.items()\n            if k in style_keys\n        }\n\n        # Ignore other attributes in case that svgelements cannot parse them\n        SVG_XMLNS = \"{http://www.w3.org/2000/svg}\"\n        new_root = ET.Element(\"svg\")\n        config_style_node = ET.SubElement(new_root, f\"{SVG_XMLNS}g\", config_style_attrs)\n        root_style_node = ET.SubElement(config_style_node, f\"{SVG_XMLNS}g\", style_attrs)\n        root_style_node.extend(root)\n        return ET.ElementTree(new_root)\n\n    def generate_config_style_dict(self) -> dict[str, str]:\n        keys_converting_dict = {\n            \"fill\": (\"color\", \"fill_color\"),\n            \"fill-opacity\": (\"opacity\", \"fill_opacity\"),\n            \"stroke\": (\"color\", \"stroke_color\"),\n            \"stroke-opacity\": (\"opacity\", \"stroke_opacity\"),\n            \"stroke-width\": (\"stroke_width\",)\n        }\n        svg_default_dict = self.svg_default\n        result = {}\n        for svg_key, style_keys in keys_converting_dict.items():\n            for style_key in style_keys:\n                if svg_default_dict[style_key] is None:\n                    continue\n                result[svg_key] = str(svg_default_dict[style_key])\n        return result\n\n    def mobjects_from_svg(self, svg: se.SVG) -> list[VMobject]:\n        result = []\n        for shape in svg.elements():\n            if isinstance(shape, (se.Group, se.Use)):\n                continue\n            elif isinstance(shape, se.Path):\n                mob = self.path_to_mobject(shape, svg)\n            elif isinstance(shape, se.SimpleLine):\n                mob = self.line_to_mobject(shape)\n            elif isinstance(shape, se.Rect):\n                mob = self.rect_to_mobject(shape)\n            elif isinstance(shape, (se.Circle, se.Ellipse)):\n                mob = self.ellipse_to_mobject(shape)\n            elif isinstance(shape, se.Polygon):\n                mob = self.polygon_to_mobject(shape)\n            elif isinstance(shape, se.Polyline):\n                mob = self.polyline_to_mobject(shape)\n            # elif isinstance(shape, se.Text):\n            #     mob = self.text_to_mobject(shape)\n            elif type(shape) == se.SVGElement:\n                continue\n            else:\n                log.warning(\"Unsupported element type: %s\", type(shape))\n                continue\n            if not mob.has_points():\n                continue\n            if isinstance(shape, se.GraphicObject):\n                self.apply_style_to_mobject(mob, shape)\n            if isinstance(shape, se.Transformable) and shape.apply:\n                self.handle_transform(mob, shape.transform)\n            result.append(mob)\n        return result\n\n    @staticmethod\n    def handle_transform(mob: VMobject, matrix: se.Matrix) -> VMobject:\n        mat = np.array([\n            [matrix.a, matrix.c],\n            [matrix.b, matrix.d]\n        ])\n        vec = np.array([matrix.e, matrix.f, 0.0])\n        mob.apply_matrix(mat)\n        mob.shift(vec)\n        return mob\n\n    @staticmethod\n    def apply_style_to_mobject(\n        mob: VMobject,\n        shape: se.GraphicObject\n    ) -> VMobject:\n        mob.set_style(\n            stroke_width=shape.stroke_width,\n            stroke_color=shape.stroke.hexrgb,\n            stroke_opacity=shape.stroke.opacity,\n            fill_color=shape.fill.hexrgb,\n            fill_opacity=shape.fill.opacity\n        )\n        return mob\n\n    def path_to_mobject(self, path: se.Path, svg: se.SVG) -> VMobjectFromSVGPath:\n        if path.id in svg.objects:\n            # If this path reuses a referenced definition (<use>), build the mobject from\n            # the original geometry.\n            # We apply the transform ourselves so we (1) keep the full precision of the \n            # reference and (2) only store one entry in PATH_TO_POINTS.\n            ref_path = svg.objects[path.id]\n            mob = VMobjectFromSVGPath(ref_path, **self.path_string_config)\n            if 'transform' in path.values:\n                matrix = se.Matrix(path.values['transform'])\n                rotation = np.array([[matrix.a, matrix.b],\n                                     [matrix.c, matrix.d]])\n                translation = np.array([[matrix.e, matrix.f]])\n                mob.apply_points_function(\n                    lambda points: np.concatenate([points[:, :2] @ rotation + translation,\n                                                   points[:, [2]]],\n                                                  axis=1),\n                    about_point=None,\n                    about_edge=None,\n                    works_on_bounding_box=False)\n            return mob\n        else:\n            return VMobjectFromSVGPath(path, **self.path_string_config)\n\n    def line_to_mobject(self, line: se.SimpleLine) -> Line:\n        return Line(\n            start=_convert_point_to_3d(line.x1, line.y1),\n            end=_convert_point_to_3d(line.x2, line.y2)\n        )\n\n    def rect_to_mobject(self, rect: se.Rect) -> Rectangle:\n        if rect.rx == 0 or rect.ry == 0:\n            mob = Rectangle(\n                width=rect.width,\n                height=rect.height,\n            )\n        else:\n            mob = RoundedRectangle(\n                width=rect.width,\n                height=rect.height * rect.rx / rect.ry,\n                corner_radius=rect.rx\n            )\n            mob.stretch_to_fit_height(rect.height)\n        mob.shift(_convert_point_to_3d(\n            rect.x + rect.width / 2,\n            rect.y + rect.height / 2\n        ))\n        return mob\n\n    def ellipse_to_mobject(self, ellipse: se.Circle | se.Ellipse) -> Circle:\n        mob = Circle(radius=ellipse.rx)\n        mob.stretch_to_fit_height(2 * ellipse.ry)\n        mob.shift(_convert_point_to_3d(\n            ellipse.cx, ellipse.cy\n        ))\n        return mob\n\n    def polygon_to_mobject(self, polygon: se.Polygon) -> Polygon:\n        points = [\n            _convert_point_to_3d(*point)\n            for point in polygon\n        ]\n        return Polygon(*points)\n\n    def polyline_to_mobject(self, polyline: se.Polyline) -> Polyline:\n        points = [\n            _convert_point_to_3d(*point)\n            for point in polyline\n        ]\n        return Polyline(*points)\n\n    def text_to_mobject(self, text: se.Text):\n        pass\n\n\nclass VMobjectFromSVGPath(VMobject):\n    def __init__(\n        self,\n        path_obj: se.Path,\n        **kwargs\n    ):\n        # caches (transform.inverse(), rot, shift)\n        self.transform_cache: tuple[se.Matrix, np.ndarray, np.ndarray] | None = None\n\n        self.path_obj = path_obj\n        super().__init__(**kwargs)\n\n    def init_points(self) -> None:\n        # After a given svg_path has been converted into points, the result\n        # will be saved so that future calls for the same pathdon't need to\n        # retrace the same computation.\n        path_string = self.path_obj.d()\n        if path_string not in PATH_TO_POINTS:\n            self.handle_commands()\n            # Save for future use\n            PATH_TO_POINTS[path_string] = self.get_points().copy()\n        else:\n            points = PATH_TO_POINTS[path_string]\n            self.set_points(points)\n\n    def handle_commands(self) -> None:\n        segment_class_to_func_map = {\n            se.Move: (self.start_new_path, (\"end\",)),\n            se.Close: (self.close_path, ()),\n            se.Line: (lambda p: self.add_line_to(p, allow_null_line=False), (\"end\",)),\n            se.QuadraticBezier: (lambda c, e: self.add_quadratic_bezier_curve_to(c, e, allow_null_curve=False), (\"control\", \"end\")),\n            se.CubicBezier: (self.add_cubic_bezier_curve_to, (\"control1\", \"control2\", \"end\"))\n        }\n        for segment in self.path_obj:\n            segment_class = segment.__class__\n            if segment_class is se.Arc:\n                self.handle_arc(segment)\n            else:\n                func, attr_names = segment_class_to_func_map[segment_class]\n                points = [\n                    _convert_point_to_3d(*segment.__getattribute__(attr_name))\n                    for attr_name in attr_names\n                ]\n                func(*points)\n\n        # Get rid of the side effect of trailing \"Z M\" commands.\n        if self.has_new_path_started():\n            self.resize_points(self.get_num_points() - 2)\n\n    def handle_arc(self, arc: se.Arc) -> None:\n        if self.transform_cache is not None:\n            transform, rot, shift = self.transform_cache\n        else:\n            # The transform obtained in this way considers the combined effect\n            # of all parent group transforms in the SVG.\n            # Therefore, the arc can be transformed inversely using this transform\n            # to correctly compute the arc path before transforming it back.\n            transform = se.Matrix(self.path_obj.values.get('transform', ''))\n            rot = np.array([\n                [transform.a, transform.c],\n                [transform.b, transform.d]\n            ])\n            shift = np.array([transform.e, transform.f, 0])\n            transform.inverse()\n            self.transform_cache = (transform, rot, shift)\n\n        # Apply inverse transformation to the arc so that its path can be correctly computed\n        arc *= transform\n\n        # The value of n_components is chosen based on the implementation of VMobject.arc_to\n        n_components = int(np.ceil(8 * abs(arc.sweep) / TAU))\n\n        # Obtain the required angular segments on the unit circle\n        arc_points = quadratic_bezier_points_for_arc(arc.sweep, n_components)\n        arc_points @= np.array(rotation_about_z(arc.get_start_t())).T\n\n        # Transform to an ellipse, considering rotation and translating the ellipse center\n        arc_points[:, 0] *= arc.rx\n        arc_points[:, 1] *= arc.ry\n        arc_points @= np.array(rotation_about_z(arc.get_rotation().as_radians)).T\n        arc_points += [*arc.center, 0]\n\n        # Transform back\n        arc_points[:, :2] @= rot.T\n        arc_points += shift\n\n        self.append_points(arc_points[1:])\n"
  },
  {
    "path": "manimlib/mobject/svg/tex_mobject.py",
    "content": "from __future__ import annotations\n\nimport re\nfrom pathlib import Path\n\nfrom functools import lru_cache\n\nfrom manimlib.config import manim_config\nfrom manimlib.mobject.svg.string_mobject import StringMobject\nfrom manimlib.mobject.svg.svg_mobject import get_svg_content_height\nfrom manimlib.mobject.types.vectorized_mobject import VGroup\nfrom manimlib.mobject.types.vectorized_mobject import VMobject\nfrom manimlib.utils.color import color_to_hex\nfrom manimlib.utils.color import hex_to_int\nfrom manimlib.utils.tex_file_writing import latex_to_svg\nfrom manimlib.utils.tex import num_tex_symbols\nfrom manimlib.logger import log\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from manimlib.typing import ManimColor, Span, Selector, Self\n\n\n@lru_cache(maxsize=1)\ndef get_tex_mob_scale_factor() -> float:\n    # Render a reference \"0\" and calibrate so that font_size_for_unit_height\n    # gives a height of 1 manim unit. Compensates for platform dvisvgm differences.\n    font_size_for_unit_height = manim_config.tex.font_size_for_unit_height\n    svg_string = latex_to_svg(\"0\", show_message_during_execution=False)\n    svg_height = get_svg_content_height(svg_string)\n    return 1.0 / (font_size_for_unit_height * svg_height)\n\n\nclass Tex(StringMobject):\n    tex_environment: str = \"align*\"\n\n    def __init__(\n        self,\n        *tex_strings: str,\n        font_size: int = 48,\n        alignment: str = R\"\\centering\",\n        template: str = \"\",\n        additional_preamble: str = \"\",\n        tex_to_color_map: dict = dict(),\n        t2c: dict = dict(),\n        isolate: Selector = [],\n        use_labelled_svg: bool = True,\n        **kwargs\n    ):\n        # Combine multi-string arg, but mark them to isolate\n        if len(tex_strings) > 1:\n            if isinstance(isolate, (str, re.Pattern, tuple)):\n                isolate = [isolate]\n            isolate = [*isolate, *tex_strings]\n\n        tex_string = (\" \".join(tex_strings)).strip()\n\n        # Prevent from passing an empty string.\n        if not tex_string.strip():\n            tex_string = R\"\\\\\"\n\n        self.tex_string = tex_string\n        self.alignment = alignment\n        self.template = template\n        self.additional_preamble = additional_preamble\n        self.tex_to_color_map = dict(**t2c, **tex_to_color_map)\n\n        super().__init__(\n            tex_string,\n            use_labelled_svg=use_labelled_svg,\n            isolate=isolate,\n            **kwargs\n        )\n\n        self.set_color_by_tex_to_color_map(self.tex_to_color_map)\n        self.scale(get_tex_mob_scale_factor() * font_size)\n\n        self.font_size = font_size  # Important for this to go after the scale call\n\n    def get_svg_string_by_content(self, content: str) -> str:\n        return latex_to_svg(content, self.template, self.additional_preamble, short_tex=self.tex_string)\n\n    def _handle_scale_side_effects(self, scale_factor: float) -> Self:\n        if hasattr(self, \"font_size\"):\n            self.font_size *= scale_factor\n        return self\n\n    # Parsing\n\n    @staticmethod\n    def get_command_matches(string: str) -> list[re.Match]:\n        # Lump together adjacent brace pairs\n        pattern = re.compile(r\"\"\"\n            (?P<command>\\\\(?:[a-zA-Z]+|.))\n            |(?P<open>{+)\n            |(?P<close>}+)\n        \"\"\", flags=re.X | re.S)\n        result = []\n        open_stack = []\n        for match_obj in pattern.finditer(string):\n            if match_obj.group(\"open\"):\n                open_stack.append((match_obj.span(), len(result)))\n            elif match_obj.group(\"close\"):\n                close_start, close_end = match_obj.span()\n                while True:\n                    if not open_stack:\n                        raise ValueError(\"Missing '{' inserted\")\n                    (open_start, open_end), index = open_stack.pop()\n                    n = min(open_end - open_start, close_end - close_start)\n                    result.insert(index, pattern.fullmatch(\n                        string, pos=open_end - n, endpos=open_end\n                    ))\n                    result.append(pattern.fullmatch(\n                        string, pos=close_start, endpos=close_start + n\n                    ))\n                    close_start += n\n                    if close_start < close_end:\n                        continue\n                    open_end -= n\n                    if open_start < open_end:\n                        open_stack.append(((open_start, open_end), index))\n                    break\n            else:\n                result.append(match_obj)\n        if open_stack:\n            raise ValueError(\"Missing '}' inserted\")\n        return result\n\n    @staticmethod\n    def get_command_flag(match_obj: re.Match) -> int:\n        if match_obj.group(\"open\"):\n            return 1\n        if match_obj.group(\"close\"):\n            return -1\n        return 0\n\n    @staticmethod\n    def replace_for_content(match_obj: re.Match) -> str:\n        return match_obj.group()\n\n    @staticmethod\n    def replace_for_matching(match_obj: re.Match) -> str:\n        if match_obj.group(\"command\"):\n            return match_obj.group()\n        return \"\"\n\n    @staticmethod\n    def get_attr_dict_from_command_pair(\n        open_command: re.Match, close_command: re.Match\n    ) -> dict[str, str] | None:\n        if len(open_command.group()) >= 2:\n            return {}\n        return None\n\n    def get_configured_items(self) -> list[tuple[Span, dict[str, str]]]:\n        return [\n            (span, {})\n            for selector in self.tex_to_color_map\n            for span in self.find_spans_by_selector(selector)\n        ]\n\n    @staticmethod\n    def get_color_command(rgb_hex: str) -> str:\n        rgb = hex_to_int(rgb_hex)\n        rg, b = divmod(rgb, 256)\n        r, g = divmod(rg, 256)\n        return f\"\\\\color[RGB]{{{r}, {g}, {b}}}\"\n\n    @staticmethod\n    def get_command_string(\n        attr_dict: dict[str, str], is_end: bool, label_hex: str | None\n    ) -> str:\n        if label_hex is None:\n            return \"\"\n        if is_end:\n            return \"}}\"\n        return \"{{\" + Tex.get_color_command(label_hex)\n\n    def get_content_prefix_and_suffix(\n        self, is_labelled: bool\n    ) -> tuple[str, str]:\n        prefix_lines = []\n        suffix_lines = []\n        if not is_labelled:\n            prefix_lines.append(self.get_color_command(\n                color_to_hex(self.base_color)\n            ))\n        if self.alignment:\n            prefix_lines.append(self.alignment)\n        if self.tex_environment:\n            prefix_lines.append(f\"\\\\begin{{{self.tex_environment}}}\")\n            suffix_lines.append(f\"\\\\end{{{self.tex_environment}}}\")\n        return (\n            \"\".join([line + \"\\n\" for line in prefix_lines]),\n            \"\".join([\"\\n\" + line for line in suffix_lines])\n        )\n\n    # Method alias\n\n    def get_parts_by_tex(self, selector: Selector) -> VGroup:\n        return self.select_parts(selector)\n\n    def get_part_by_tex(self, selector: Selector, index: int = 0) -> VMobject:\n        return self.select_part(selector, index)\n\n    def set_color_by_tex(self, selector: Selector, color: ManimColor):\n        return self.set_parts_color(selector, color)\n\n    def set_color_by_tex_to_color_map(\n        self, color_map: dict[Selector, ManimColor]\n    ):\n        return self.set_parts_color_by_dict(color_map)\n\n    def get_tex(self) -> str:\n        return self.get_string()\n\n    # Specific to Tex\n    def substr_to_path_count(self, substr: str) -> int:\n        tex = self.get_tex()\n        if len(self) != num_tex_symbols(tex):\n            log.warning(f\"Estimated size of {tex} does not match true size\")\n        return num_tex_symbols(substr)\n\n    def get_symbol_substrings(self):\n        pattern = \"|\".join((\n            # Tex commands\n            r\"\\\\[a-zA-Z]+\",\n            # And most single characters, with these exceptions\n            r\"[^\\^\\{\\}\\s\\_\\$\\\\\\&]\",\n        ))\n        return re.findall(pattern, self.string)\n\n    def make_number_changeable(\n        self,\n        value: float | int | str,\n        index: int = 0,\n        replace_all: bool = False,\n        **config,\n    ) -> VMobject:\n        substr = str(value)\n        parts = self.select_parts(substr)\n        if len(parts) == 0:\n            log.warning(f\"{value} not found in Tex.make_number_changeable call\")\n            return VMobject()\n        if index > len(parts) - 1:\n            log.warning(f\"Requested {index}th occurance of {value}, but only {len(parts)} exist\")\n            return VMobject()\n        if not replace_all:\n            parts = [parts[index]]\n\n        from manimlib.mobject.numbers import DecimalNumber\n\n        decimal_mobs = []\n        for part in parts:\n            if \"num_decimal_places\" not in config:\n                ndp = len(substr.split(\".\")[1]) if \".\" in substr else 0\n                config[\"num_decimal_places\"] = ndp\n            decimal_mob = DecimalNumber(float(value), **config)\n            decimal_mob.replace(part)\n            decimal_mob.match_style(part)\n            if len(part) > 1:\n                self.remove(*part[1:])\n            self.replace_submobject(self.submobjects.index(part[0]), decimal_mob)\n            decimal_mobs.append(decimal_mob)\n\n            # Replace substr with something that looks like a tex command. This\n            # is to ensure Tex.substr_to_path_count counts it correctly.\n            self.string = self.string.replace(substr, R\"\\decimalmob\", 1)\n\n        if replace_all:\n            return VGroup(*decimal_mobs)\n        return decimal_mobs[index]\n\n\nclass TexText(Tex):\n    tex_environment: str = \"\"\n"
  },
  {
    "path": "manimlib/mobject/svg/text_mobject.py",
    "content": "from __future__ import annotations\n\nfrom contextlib import contextmanager\nimport os\nfrom pathlib import Path\nimport re\nimport tempfile\nfrom functools import lru_cache\n\nimport manimpango\nimport pygments\nimport pygments.formatters\nimport pygments.lexers\n\nfrom manimlib.config import manim_config\nfrom manimlib.constants import DEFAULT_PIXEL_WIDTH, FRAME_WIDTH\nfrom manimlib.constants import NORMAL\nfrom manimlib.logger import log\nfrom manimlib.mobject.svg.string_mobject import StringMobject\nfrom manimlib.mobject.svg.svg_mobject import get_svg_content_height\nfrom manimlib.utils.cache import cache_on_disk\nfrom manimlib.utils.color import color_to_hex\nfrom manimlib.utils.color import int_to_hex\nfrom manimlib.utils.simple_functions import hash_string\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Iterable\n\n    from manimlib.mobject.types.vectorized_mobject import VGroup\n    from manimlib.typing import ManimColor, Span, Selector\n\n\nDEFAULT_LINE_SPACING_SCALE = 0.6\n# Ensure the canvas is large enough to hold all glyphs.\nDEFAULT_CANVAS_WIDTH = 16384\nDEFAULT_CANVAS_HEIGHT = 16384\n\n\n# Temporary handler\nclass _Alignment:\n    VAL_DICT = {\n        \"LEFT\": 0,\n        \"CENTER\": 1,\n        \"RIGHT\": 2\n    }\n\n    def __init__(self, s: str):\n        self.value = _Alignment.VAL_DICT[s.upper()]\n\n\n@lru_cache(maxsize=128)\n@cache_on_disk\ndef markup_to_svg(\n    markup_str: str,\n    justify: bool = False,\n    indent: float = 0,\n    alignment: str = \"CENTER\",\n    line_width: float | None = None,\n) -> str:\n    validate_error = manimpango.MarkupUtils.validate(markup_str)\n    if validate_error:\n        raise ValueError(\n            f\"Invalid markup string \\\"{markup_str}\\\"\\n\" + \\\n            f\"{validate_error}\"\n        )\n\n    # `manimpango` is under construction,\n    # so the following code is intended to suit its interface\n    alignment = _Alignment(alignment)\n    if line_width is None:\n        pango_width = -1\n    else:\n        pango_width = line_width / FRAME_WIDTH * DEFAULT_PIXEL_WIDTH\n\n    # Write the result to a temporary svg file, and return it's contents.\n    temp_file = Path(tempfile.gettempdir(), hash_string(markup_str)).with_suffix(\".svg\")\n    manimpango.MarkupUtils.text2svg(\n        text=markup_str,\n        font=\"\",                     # Already handled\n        slant=\"NORMAL\",              # Already handled\n        weight=\"NORMAL\",             # Already handled\n        size=1,                      # Already handled\n        _=0,                         # Empty parameter\n        disable_liga=False,\n        file_name=str(temp_file),\n        START_X=0,\n        START_Y=0,\n        width=DEFAULT_CANVAS_WIDTH,\n        height=DEFAULT_CANVAS_HEIGHT,\n        justify=justify,\n        indent=indent,\n        line_spacing=None,           # Already handled\n        alignment=alignment,\n        pango_width=pango_width\n    )\n    result = temp_file.read_text()\n    os.remove(temp_file)\n    return result\n\n\n@lru_cache(maxsize=1)\ndef get_text_mob_scale_factor() -> float:\n    # Render a reference \"0\" and calibrate so that font_size_for_unit_height\n    # gives a height of 1 manim unit. Compensates for platform DPI differences.\n    ref_size = 48\n    font_size_for_unit_height = manim_config.text.font_size_for_unit_height\n    pango_size = str(round(ref_size * 1024))\n    svg_string = markup_to_svg(f'<span font_size=\"{pango_size}\">0</span>')\n    svg_height = get_svg_content_height(svg_string)\n    return ref_size / (font_size_for_unit_height * svg_height)\n\n\nclass MarkupText(StringMobject):\n    # See https://docs.gtk.org/Pango/pango_markup.html\n    MARKUP_TAGS = {\n        \"b\": {\"font_weight\": \"bold\"},\n        \"big\": {\"font_size\": \"larger\"},\n        \"i\": {\"font_style\": \"italic\"},\n        \"s\": {\"strikethrough\": \"true\"},\n        \"sub\": {\"baseline_shift\": \"subscript\", \"font_scale\": \"subscript\"},\n        \"sup\": {\"baseline_shift\": \"superscript\", \"font_scale\": \"superscript\"},\n        \"small\": {\"font_size\": \"smaller\"},\n        \"tt\": {\"font_family\": \"monospace\"},\n        \"u\": {\"underline\": \"single\"},\n    }\n    MARKUP_ENTITY_DICT = {\n        \"<\": \"&lt;\",\n        \">\": \"&gt;\",\n        \"&\": \"&amp;\",\n        \"\\\"\": \"&quot;\",\n        \"'\": \"&apos;\"\n    }\n\n    def __init__(\n        self,\n        text: str,\n        font_size: int = 48,\n        height: float | None = None,\n        justify: bool = False,\n        indent: float = 0,\n        alignment: str = \"\",\n        line_width: float | None = None,\n        font: str = \"\",\n        slant: str = NORMAL,\n        weight: str = NORMAL,\n        gradient: Iterable[ManimColor] | None = None,\n        line_spacing_height: float | None = None,\n        text2color: dict = {},\n        text2font: dict = {},\n        text2gradient: dict = {},\n        text2slant: dict = {},\n        text2weight: dict = {},\n        # For convenience, one can use shortened names\n        lsh: float | None = None,  # Overrides line_spacing_height\n        t2c: dict = {},  # Overrides text2color if nonempty\n        t2f: dict = {},  # Overrides text2font if nonempty\n        t2g: dict = {},  # Overrides text2gradient if nonempty\n        t2s: dict = {},  # Overrides text2slant if nonempty\n        t2w: dict = {},  # Overrides text2weight if nonempty\n        global_config: dict = {},\n        local_configs: dict = {},\n        disable_ligatures: bool = True,\n        isolate: Selector = re.compile(r\"\\w+\", re.U),\n        **kwargs\n    ):\n        text_config = manim_config.text\n        self.text = text\n        self.font_size = font_size\n        self.justify = justify\n        self.indent = indent\n        self.alignment = alignment or text_config.alignment\n        self.line_width = line_width\n        self.font = font or text_config.font\n        self.slant = slant\n        self.weight = weight\n\n        self.lsh = line_spacing_height or lsh\n        self.t2c = text2color or t2c\n        self.t2f = text2font or t2f\n        self.t2g = text2gradient or t2g\n        self.t2s = text2slant or t2s\n        self.t2w = text2weight or t2w\n\n        self.global_config = global_config\n        self.local_configs = local_configs\n        self.disable_ligatures = disable_ligatures\n        self.isolate = isolate\n\n        super().__init__(text, height=height, **kwargs)\n\n        if self.t2g:\n            log.warning(\"\"\"\n                Manim currently cannot parse gradient from svg.\n                Please set gradient via `set_color_by_gradient`.\n            \"\"\")\n        if gradient:\n            self.set_color_by_gradient(*gradient)\n        if self.t2c:\n            self.set_color_by_text_to_color_map(self.t2c)\n        if height is None:\n            self.scale(get_text_mob_scale_factor())\n\n    def get_svg_string_by_content(self, content: str) -> str:\n        self.content = content\n        return markup_to_svg(\n            content,\n            justify=self.justify,\n            indent=self.indent,\n            alignment=self.alignment,\n            line_width=self.line_width\n        )\n\n    # Toolkits\n\n    @staticmethod\n    def escape_markup_char(substr: str) -> str:\n        return MarkupText.MARKUP_ENTITY_DICT.get(substr, substr)\n\n    @staticmethod\n    def unescape_markup_char(substr: str) -> str:\n        return {\n            v: k\n            for k, v in MarkupText.MARKUP_ENTITY_DICT.items()\n        }.get(substr, substr)\n\n    # Parsing\n\n    @staticmethod\n    def get_command_matches(string: str) -> list[re.Match]:\n        pattern = re.compile(r\"\"\"\n            (?P<tag>\n                <\n                (?P<close_slash>/)?\n                (?P<tag_name>\\w+)\\s*\n                (?P<attr_list>(?:\\w+\\s*\\=\\s*(?P<quot>[\"']).*?(?P=quot)\\s*)*)\n                (?P<elision_slash>/)?\n                >\n            )\n            |(?P<passthrough>\n                <\\?.*?\\?>|<!--.*?-->|<!\\[CDATA\\[.*?\\]\\]>|<!DOCTYPE.*?>\n            )\n            |(?P<entity>&(?P<unicode>\\#(?P<hex>x)?)?(?P<content>.*?);)\n            |(?P<char>[>\"'])\n        \"\"\", flags=re.X | re.S)\n        return list(pattern.finditer(string))\n\n    @staticmethod\n    def get_command_flag(match_obj: re.Match) -> int:\n        if match_obj.group(\"tag\"):\n            if match_obj.group(\"close_slash\"):\n                return -1\n            if not match_obj.group(\"elision_slash\"):\n                return 1\n        return 0\n\n    @staticmethod\n    def replace_for_content(match_obj: re.Match) -> str:\n        if match_obj.group(\"tag\"):\n            return \"\"\n        if match_obj.group(\"char\"):\n            return MarkupText.escape_markup_char(match_obj.group(\"char\"))\n        return match_obj.group()\n\n    @staticmethod\n    def replace_for_matching(match_obj: re.Match) -> str:\n        if match_obj.group(\"tag\") or match_obj.group(\"passthrough\"):\n            return \"\"\n        if match_obj.group(\"entity\"):\n            if match_obj.group(\"unicode\"):\n                base = 10\n                if match_obj.group(\"hex\"):\n                    base = 16\n                return chr(int(match_obj.group(\"content\"), base))\n            return MarkupText.unescape_markup_char(match_obj.group(\"entity\"))\n        return match_obj.group()\n\n    @staticmethod\n    def get_attr_dict_from_command_pair(\n        open_command: re.Match, close_command: re.Match\n    ) -> dict[str, str] | None:\n        pattern = r\"\"\"\n            (?P<attr_name>\\w+)\n            \\s*\\=\\s*\n            (?P<quot>[\"'])(?P<attr_val>.*?)(?P=quot)\n        \"\"\"\n        tag_name = open_command.group(\"tag_name\")\n        if tag_name == \"span\":\n            return {\n                match_obj.group(\"attr_name\"): match_obj.group(\"attr_val\")\n                for match_obj in re.finditer(\n                    pattern, open_command.group(\"attr_list\"), re.S | re.X\n                )\n            }\n        return MarkupText.MARKUP_TAGS.get(tag_name, {})\n\n    def get_configured_items(self) -> list[tuple[Span, dict[str, str]]]:\n        return [\n            *(\n                (span, {key: val})\n                for t2x_dict, key in (\n                    (self.t2c, \"foreground\"),\n                    (self.t2f, \"font_family\"),\n                    (self.t2s, \"font_style\"),\n                    (self.t2w, \"font_weight\")\n                )\n                for selector, val in t2x_dict.items()\n                for span in self.find_spans_by_selector(selector)\n            ),\n            *(\n                (span, local_config)\n                for selector, local_config in self.local_configs.items()\n                for span in self.find_spans_by_selector(selector)\n            )\n        ]\n\n    @staticmethod\n    def get_command_string(\n        attr_dict: dict[str, str], is_end: bool, label_hex: str | None\n    ) -> str:\n        if is_end:\n            return \"</span>\"\n\n        if label_hex is not None:\n            converted_attr_dict = {\"foreground\": label_hex}\n            for key, val in attr_dict.items():\n                if key in (\n                    \"background\", \"bgcolor\",\n                    \"underline_color\", \"overline_color\", \"strikethrough_color\"\n                ):\n                    converted_attr_dict[key] = \"black\"\n                elif key not in (\"foreground\", \"fgcolor\", \"color\"):\n                    converted_attr_dict[key] = val\n        else:\n            converted_attr_dict = attr_dict.copy()\n        attrs_str = \" \".join([\n            f\"{key}='{val}'\"\n            for key, val in converted_attr_dict.items()\n        ])\n        return f\"<span {attrs_str}>\"\n\n    def get_content_prefix_and_suffix(\n        self, is_labelled: bool\n    ) -> tuple[str, str]:\n        global_attr_dict = {\n            \"foreground\": color_to_hex(self.base_color),\n            \"font_family\": self.font,\n            \"font_style\": self.slant,\n            \"font_weight\": self.weight,\n            \"font_size\": str(round(self.font_size * 1024)),\n        }\n        # `line_height` attribute is supported since Pango 1.50.\n        pango_version = manimpango.pango_version()\n        if tuple(map(int, pango_version.split(\".\"))) < (1, 50):\n            if self.lsh is not None:\n                log.warning(\n                    \"Pango version %s found (< 1.50), \"\n                    \"unable to set `line_height` attribute\",\n                    pango_version\n                )\n        else:\n            line_spacing_scale = self.lsh or DEFAULT_LINE_SPACING_SCALE\n            global_attr_dict[\"line_height\"] = str(\n                ((line_spacing_scale) + 1) * 0.6\n            )\n        if self.disable_ligatures:\n            global_attr_dict[\"font_features\"] = \"liga=0,dlig=0,clig=0,hlig=0\"\n\n        global_attr_dict.update(self.global_config)\n        return tuple(\n            self.get_command_string(\n                global_attr_dict,\n                is_end=is_end,\n                label_hex=int_to_hex(0) if is_labelled else None\n            )\n            for is_end in (False, True)\n        )\n\n    # Method alias\n\n    def get_parts_by_text(self, selector: Selector) -> VGroup:\n        return self.select_parts(selector)\n\n    def get_part_by_text(self, selector: Selector, **kwargs) -> VGroup:\n        return self.select_part(selector, **kwargs)\n\n    def set_color_by_text(self, selector: Selector, color: ManimColor):\n        return self.set_parts_color(selector, color)\n\n    def set_color_by_text_to_color_map(\n        self, color_map: dict[Selector, ManimColor]\n    ):\n        return self.set_parts_color_by_dict(color_map)\n\n    def get_text(self) -> str:\n        return self.get_string()\n\n\nclass Text(MarkupText):\n    def __init__(\n        self,\n        text: str,\n        # For backward compatibility\n        isolate: Selector = (re.compile(r\"\\w+\", re.U), re.compile(r\"\\S+\", re.U)),\n        use_labelled_svg: bool = True,\n        path_string_config: dict = dict(\n            use_simple_quadratic_approx=True,\n        ),\n        **kwargs\n    ):\n        super().__init__(\n            text,\n            isolate=isolate,\n            use_labelled_svg=use_labelled_svg,\n            path_string_config=path_string_config,\n            **kwargs\n        )\n\n    @staticmethod\n    def get_command_matches(string: str) -> list[re.Match]:\n        pattern = re.compile(r\"\"\"[<>&\"']\"\"\")\n        return list(pattern.finditer(string))\n\n    @staticmethod\n    def get_command_flag(match_obj: re.Match) -> int:\n        return 0\n\n    @staticmethod\n    def replace_for_content(match_obj: re.Match) -> str:\n        return Text.escape_markup_char(match_obj.group())\n\n    @staticmethod\n    def replace_for_matching(match_obj: re.Match) -> str:\n        return match_obj.group()\n\n\nclass Code(MarkupText):\n    def __init__(\n        self,\n        code: str,\n        font: str = \"Consolas\",\n        font_size: int = 24,\n        lsh: float = 1.0,\n        fill_color: ManimColor = None,\n        stroke_color: ManimColor = None,\n        language: str = \"python\",\n        # Visit https://pygments.org/demo/ to have a preview of more styles.\n        code_style: str = \"monokai\",\n        **kwargs\n    ):\n        lexer = pygments.lexers.get_lexer_by_name(language)\n        formatter = pygments.formatters.PangoMarkupFormatter(\n            style=code_style\n        )\n        markup = pygments.highlight(code, lexer, formatter)\n        markup = re.sub(r\"</?tt>\", \"\", markup)\n        super().__init__(\n            markup,\n            font=font,\n            font_size=font_size,\n            lsh=lsh,\n            stroke_color=stroke_color,\n            fill_color=fill_color,\n            **kwargs\n        )\n\n\n@contextmanager\ndef register_font(font_file: str | Path):\n    \"\"\"Temporarily add a font file to Pango's search path.\n    This searches for the font_file at various places. The order it searches it described below.\n    1. Absolute path.\n    2. Downloads dir.\n\n    Parameters\n    ----------\n    font_file :\n        The font file to add.\n    Examples\n    --------\n    Use ``with register_font(...)`` to add a font file to search\n    path.\n    .. code-block:: python\n        with register_font(\"path/to/font_file.ttf\"):\n           a = Text(\"Hello\", font=\"Custom Font Name\")\n    Raises\n    ------\n    FileNotFoundError:\n        If the font doesn't exists.\n    AttributeError:\n        If this method is used on macOS.\n    Notes\n    -----\n    This method of adding font files also works with :class:`CairoText`.\n    .. important ::\n        This method is available for macOS for ``ManimPango>=v0.2.3``. Using this\n        method with previous releases will raise an :class:`AttributeError` on macOS.\n    \"\"\"\n\n    file_path = Path(font_file).resolve()\n    if not file_path.exists():\n        error = f\"Can't find {font_file}.\"\n        raise FileNotFoundError(error)\n    try:\n        assert manimpango.register_font(str(file_path))\n        yield\n    finally:\n        manimpango.unregister_font(str(file_path))\n"
  },
  {
    "path": "manimlib/mobject/three_dimensions.py",
    "content": "from __future__ import annotations\n\nimport math\n\nimport numpy as np\n\nfrom manimlib.constants import BLUE, BLUE_D, BLUE_E, GREY_A, BLACK\nfrom manimlib.constants import IN, ORIGIN, OUT, RIGHT\nfrom manimlib.constants import PI, TAU\nfrom manimlib.mobject.mobject import Mobject\nfrom manimlib.mobject.types.surface import SGroup\nfrom manimlib.mobject.types.surface import Surface\nfrom manimlib.mobject.types.vectorized_mobject import VGroup\nfrom manimlib.mobject.types.vectorized_mobject import VMobject\nfrom manimlib.mobject.geometry import Polygon\nfrom manimlib.mobject.geometry import Square\nfrom manimlib.utils.bezier import interpolate\nfrom manimlib.utils.iterables import adjacent_pairs\nfrom manimlib.utils.space_ops import compass_directions\nfrom manimlib.utils.space_ops import get_norm\nfrom manimlib.utils.space_ops import z_to_vector\n\nfrom typing import TYPE_CHECKING\nif TYPE_CHECKING:\n    from typing import Tuple, TypeVar\n    from manimlib.typing import ManimColor, Vect3, Sequence\n\n    T = TypeVar(\"T\", bound=Mobject)\n\n\nclass SurfaceMesh(VGroup):\n    def __init__(\n        self,\n        uv_surface: Surface,\n        resolution: Tuple[int, int] = (21, 11),\n        stroke_width: float = 1,\n        stroke_color: ManimColor = GREY_A,\n        normal_nudge: float = 1e-2,\n        depth_test: bool = True,\n        joint_type: str = 'no_joint',\n        **kwargs\n    ):\n        self.uv_surface = uv_surface\n        self.resolution = resolution\n        self.normal_nudge = normal_nudge\n\n        super().__init__(\n            stroke_color=stroke_color,\n            stroke_width=stroke_width,\n            depth_test=depth_test,\n            joint_type=joint_type,\n            **kwargs\n        )\n\n    def init_points(self) -> None:\n        uv_surface = self.uv_surface\n\n        full_nu, full_nv = uv_surface.resolution\n        part_nu, part_nv = self.resolution\n        # 'indices' are treated as floats. Later, there will be\n        # an interpolation between the floor and ceiling of these\n        # indices\n        u_indices = np.linspace(0, full_nu - 1, part_nu)\n        v_indices = np.linspace(0, full_nv - 1, part_nv)\n\n        points = uv_surface.get_points()\n        normals = uv_surface.get_unit_normals()\n        nudge = self.normal_nudge\n        nudged_points = points + nudge * normals\n\n        for ui in u_indices:\n            path = VMobject()\n            low_ui = full_nv * int(math.floor(ui))\n            high_ui = full_nv * int(math.ceil(ui))\n            path.set_points_smoothly(interpolate(\n                nudged_points[low_ui:low_ui + full_nv],\n                nudged_points[high_ui:high_ui + full_nv],\n                ui % 1\n            ))\n            self.add(path)\n        for vi in v_indices:\n            path = VMobject()\n            path.set_points_smoothly(interpolate(\n                nudged_points[int(math.floor(vi))::full_nv],\n                nudged_points[int(math.ceil(vi))::full_nv],\n                vi % 1\n            ))\n            self.add(path)\n\n\n# 3D shapes\n\nclass Sphere(Surface):\n    def __init__(\n        self,\n        u_range: Tuple[float, float] = (0, TAU),\n        v_range: Tuple[float, float] = (0, PI),\n        resolution: Tuple[int, int] = (101, 51),\n        radius: float = 1.0,\n        true_normals: bool = True,\n        clockwise=False,\n        **kwargs,\n    ):\n        self.radius = radius\n        self.clockwise = clockwise\n        super().__init__(\n            u_range=u_range,\n            v_range=v_range,\n            resolution=resolution,\n            **kwargs\n        )\n        # Add bespoke normal specification to avoid issue at poles\n        if true_normals:\n            self.data['d_normal_point'] = self.data['point'] * ((radius + self.normal_nudge) / radius)\n\n    def uv_func(self, u: float, v: float) -> np.ndarray:\n        sign = -1 if self.clockwise else +1\n        return self.radius * np.array([\n            math.cos(sign * u) * math.sin(v),\n            math.sin(sign * u) * math.sin(v),\n            -math.cos(v)\n        ])\n\n\nclass Torus(Surface):\n    def __init__(\n        self,\n        u_range: Tuple[float, float] = (0, TAU),\n        v_range: Tuple[float, float] = (0, TAU),\n        r1: float = 3.0,\n        r2: float = 1.0,\n        **kwargs,\n    ):\n        self.r1 = r1\n        self.r2 = r2\n        super().__init__(\n            u_range=u_range,\n            v_range=v_range,\n            **kwargs,\n        )\n\n    def uv_func(self, u: float, v: float) -> np.ndarray:\n        P = np.array([math.cos(u), math.sin(u), 0])\n        return (self.r1 - self.r2 * math.cos(v)) * P - self.r2 * math.sin(v) * OUT\n\n\nclass Cylinder(Surface):\n    def __init__(\n        self,\n        u_range: Tuple[float, float] = (0, TAU),\n        v_range: Tuple[float, float] = (-1, 1),\n        resolution: Tuple[int, int] = (101, 11),\n        height: float = 2,\n        radius: float = 1,\n        axis: Vect3 = OUT,\n        **kwargs,\n    ):\n        self.height = height\n        self.radius = radius\n        self.axis = axis\n        super().__init__(\n            u_range=u_range,\n            v_range=v_range,\n            resolution=resolution,\n            **kwargs\n        )\n\n    def init_points(self):\n        super().init_points()\n        self.scale(self.radius)\n        self.set_depth(self.height, stretch=True)\n        self.apply_matrix(z_to_vector(self.axis))\n\n    def uv_func(self, u: float, v: float) -> np.ndarray:\n        return np.array([np.cos(u), np.sin(u), v])\n\n\nclass Cone(Cylinder):\n    def __init__(\n        self,\n        u_range: Tuple[float, float] = (0, TAU),\n        v_range: Tuple[float, float] = (0, 1),\n        *args,\n        **kwargs,\n    ):\n        super().__init__(u_range=u_range, v_range=v_range, *args, **kwargs)\n\n    def uv_func(self, u: float, v: float) -> np.ndarray:\n        return np.array([(1 - v) * np.cos(u), (1 - v) * np.sin(u), v])\n\n\nclass Line3D(Cylinder):\n    def __init__(\n        self,\n        start: Vect3,\n        end: Vect3,\n        width: float = 0.05,\n        resolution: Tuple[int, int] = (21, 25),\n        **kwargs\n    ):\n        axis = end - start\n        super().__init__(\n            height=get_norm(axis),\n            radius=width / 2,\n            axis=axis,\n            resolution=resolution,\n            **kwargs\n        )\n        self.shift((start + end) / 2)\n\n\nclass Disk3D(Surface):\n    def __init__(\n        self,\n        radius: float = 1,\n        u_range: Tuple[float, float] = (0, 1),\n        v_range: Tuple[float, float] = (0, TAU),\n        resolution: Tuple[int, int] = (2, 100),\n        **kwargs\n    ):\n        super().__init__(\n            u_range=u_range,\n            v_range=v_range,\n            resolution=resolution,\n            **kwargs,\n        )\n        self.scale(radius)\n\n    def uv_func(self, u: float, v: float) -> np.ndarray:\n        return np.array([\n            u * math.cos(v),\n            u * math.sin(v),\n            0\n        ])\n\n\nclass Square3D(Surface):\n    def __init__(\n        self,\n        side_length: float = 2.0,\n        u_range: Tuple[float, float] = (-1, 1),\n        v_range: Tuple[float, float] = (-1, 1),\n        resolution: Tuple[int, int] = (2, 2),\n        **kwargs,\n    ):\n        super().__init__(\n            u_range=u_range, \n            v_range=v_range, \n            resolution=resolution, \n            **kwargs\n        )\n        self.scale(side_length / 2)\n\n    def uv_func(self, u: float, v: float) -> np.ndarray:\n        return np.array([u, v, 0])\n\n\ndef square_to_cube_faces(square: T) -> list[T]:\n    radius = square.get_height() / 2\n    square.move_to(radius * OUT)\n    result = [square.copy()]\n    result.extend([\n        square.copy().rotate(PI / 2, axis=vect, about_point=ORIGIN)\n        for vect in compass_directions(4)\n    ])\n    result.append(square.copy().rotate(PI, RIGHT, about_point=ORIGIN))\n    return result\n\n\nclass Cube(SGroup):\n    def __init__(\n        self,\n        color: ManimColor = BLUE,\n        opacity: float = 1,\n        shading: Tuple[float, float, float] = (0.1, 0.5, 0.1),\n        square_resolution: Tuple[int, int] = (2, 2),\n        side_length: float = 2,\n        **kwargs,\n    ):\n        face = Square3D(\n            resolution=square_resolution,\n            side_length=side_length,\n            color=color,\n            opacity=opacity,\n            shading=shading,\n        )\n        super().__init__(*square_to_cube_faces(face), **kwargs)\n\n\nclass Prism(Cube):\n    def __init__(\n        self,\n        width: float = 3.0,\n        height: float = 2.0,\n        depth: float = 1.0,\n        **kwargs\n    ):\n        super().__init__(**kwargs)\n        for dim, value in enumerate([width, height, depth]):\n            self.rescale_to_fit(value, dim, stretch=True)\n\n\nclass VGroup3D(VGroup):\n    def __init__(\n        self,\n        *vmobjects: VMobject,\n        depth_test: bool = True,\n        shading: Tuple[float, float, float] = (0.2, 0.2, 0.2),\n        joint_type: str = \"no_joint\",\n        **kwargs\n    ):\n        super().__init__(*vmobjects, **kwargs)\n        self.set_shading(*shading)\n        self.set_joint_type(joint_type)\n        if depth_test:\n            self.apply_depth_test()\n\n\nclass VCube(VGroup3D):\n    def __init__(\n        self,\n        side_length: float = 2.0,\n        fill_color: ManimColor = BLUE_D,\n        fill_opacity: float = 1,\n        stroke_width: float = 0,\n        **kwargs\n    ):\n        style = dict(\n            fill_color=fill_color,\n            fill_opacity=fill_opacity,\n            stroke_width=stroke_width,\n            **kwargs\n        )\n        face = Square(side_length=side_length, **style)\n        super().__init__(*square_to_cube_faces(face), **style)\n\n\nclass VPrism(VCube):\n    def __init__(\n        self,\n        width: float = 3.0,\n        height: float = 2.0,\n        depth: float = 1.0,\n        **kwargs\n    ):\n        super().__init__(**kwargs)\n        for dim, value in enumerate([width, height, depth]):\n            self.rescale_to_fit(value, dim, stretch=True)\n\n\nclass Dodecahedron(VGroup3D):\n    def __init__(\n        self,\n        fill_color: ManimColor = BLUE_E,\n        fill_opacity: float = 1,\n        stroke_color: ManimColor = BLUE_E,\n        stroke_width: float = 1,\n        shading: Tuple[float, float, float] = (0.2, 0.2, 0.2),\n        **kwargs,\n    ):\n        style = dict(\n            fill_color=fill_color,\n            fill_opacity=fill_opacity,\n            stroke_color=stroke_color,\n            stroke_width=stroke_width,\n            shading=shading,\n            **kwargs\n        )\n\n        # Start by creating two of the pentagons, meeting\n        # back to back on the positive x-axis\n        phi = (1 + math.sqrt(5)) / 2\n        x, y, z = np.identity(3)\n        pentagon1 = Polygon(\n            np.array([phi, 1 / phi, 0]),\n            np.array([1, 1, 1]),\n            np.array([1 / phi, 0, phi]),\n            np.array([1, -1, 1]),\n            np.array([phi, -1 / phi, 0]),\n            **style\n        )\n        pentagon2 = pentagon1.copy().stretch(-1, 2, about_point=ORIGIN)\n        pentagon2.reverse_points()\n        x_pair = VGroup(pentagon1, pentagon2)\n        z_pair = x_pair.copy().apply_matrix(np.array([z, -x, -y]).T)\n        y_pair = x_pair.copy().apply_matrix(np.array([y, z, x]).T)\n\n        pentagons = [*x_pair, *y_pair, *z_pair]\n        for pentagon in list(pentagons):\n            pc = pentagon.copy()\n            pc.apply_function(lambda p: -p)\n            pc.reverse_points()\n            pentagons.append(pc)\n\n        super().__init__(*pentagons, **style)\n\n\nclass Prismify(VGroup3D):\n    def __init__(self, vmobject, depth=1.0, direction=IN, **kwargs):\n        # At the moment, this assume stright edges\n        vect = depth * direction\n        pieces = [vmobject.copy()]\n        points = vmobject.get_anchors()\n        for p1, p2 in adjacent_pairs(points):\n            wall = VMobject()\n            wall.match_style(vmobject)\n            wall.set_points_as_corners([p1, p2, p2 + vect, p1 + vect])\n            pieces.append(wall)\n        top = vmobject.copy()\n        top.shift(vect)\n        top.reverse_points()\n        pieces.append(top)\n        super().__init__(*pieces, **kwargs)\n"
  },
  {
    "path": "manimlib/mobject/types/__init__.py",
    "content": ""
  },
  {
    "path": "manimlib/mobject/types/dot_cloud.py",
    "content": "from __future__ import annotations\n\nimport moderngl\nimport numpy as np\n\nfrom manimlib.constants import GREY_C, YELLOW\nfrom manimlib.constants import ORIGIN, NULL_POINTS\nfrom manimlib.mobject.mobject import Mobject\nfrom manimlib.mobject.types.point_cloud_mobject import PMobject\nfrom manimlib.utils.iterables import resize_with_interpolation\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    import numpy.typing as npt\n    from typing import Sequence, Tuple\n    from manimlib.typing import ManimColor, Vect3, Vect3Array, Self\n\n\nDEFAULT_DOT_RADIUS = 0.05\nDEFAULT_GLOW_DOT_RADIUS = 0.2\nDEFAULT_GRID_HEIGHT = 6\nDEFAULT_BUFF_RATIO = 0.5\n\n\nclass DotCloud(PMobject):\n    shader_folder: str = \"true_dot\"\n    render_primitive: int = moderngl.POINTS\n    data_dtype: Sequence[Tuple[str, type, Tuple[int]]] = [\n        ('point', np.float32, (3,)),\n        ('radius', np.float32, (1,)),\n        ('rgba', np.float32, (4,)),\n    ]\n\n    def __init__(\n        self,\n        points: Vect3Array = NULL_POINTS,\n        color: ManimColor = GREY_C,\n        opacity: float = 1.0,\n        radius: float = DEFAULT_DOT_RADIUS,\n        glow_factor: float = 0.0,\n        anti_alias_width: float = 2.0,\n        **kwargs\n    ):\n        self.radius = radius\n        self.glow_factor = glow_factor\n        self.anti_alias_width = anti_alias_width\n\n        super().__init__(\n            color=color,\n            opacity=opacity,\n            **kwargs\n        )\n        self.set_radius(self.radius)\n\n        if points is not None:\n            self.set_points(points)\n\n    def init_uniforms(self) -> None:\n        super().init_uniforms()\n        self.uniforms[\"glow_factor\"] = self.glow_factor\n        self.uniforms[\"anti_alias_width\"] = self.anti_alias_width\n\n    def to_grid(\n        self,\n        n_rows: int,\n        n_cols: int,\n        n_layers: int = 1,\n        buff_ratio: float | None = None,\n        h_buff_ratio: float = 1.0,\n        v_buff_ratio: float = 1.0,\n        d_buff_ratio: float = 1.0,\n        height: float = DEFAULT_GRID_HEIGHT,\n    ) -> Self:\n        n_points = n_rows * n_cols * n_layers\n        points = np.repeat(range(n_points), 3, axis=0).reshape((n_points, 3))\n        points[:, 0] = points[:, 0] % n_cols\n        points[:, 1] = (points[:, 1] // n_cols) % n_rows\n        points[:, 2] = points[:, 2] // (n_rows * n_cols)\n        self.set_points(points.astype(float))\n\n        if buff_ratio is not None:\n            v_buff_ratio = buff_ratio\n            h_buff_ratio = buff_ratio\n            d_buff_ratio = buff_ratio\n\n        radius = self.get_radius()\n        ns = [n_cols, n_rows, n_layers]\n        brs = [h_buff_ratio, v_buff_ratio, d_buff_ratio]\n        self.set_radius(0)\n        for n, br, dim in zip(ns, brs, range(3)):\n            self.rescale_to_fit(2 * radius * (1 + br) * (n - 1), dim, stretch=True)\n        self.set_radius(radius)\n        if height is not None:\n            self.set_height(height)\n        self.center()\n        return self\n\n    @Mobject.affects_data\n    def set_radii(self, radii: npt.ArrayLike) -> Self:\n        n_points = self.get_num_points()\n        radii = np.array(radii).reshape((len(radii), 1))\n        self.data[\"radius\"][:] = resize_with_interpolation(radii, n_points)\n        self.refresh_bounding_box()\n        return self\n\n    def get_radii(self) -> np.ndarray:\n        return self.data[\"radius\"]\n\n    @Mobject.affects_data\n    def set_radius(self, radius: float) -> Self:\n        data = self.data if self.get_num_points() > 0 else self._data_defaults\n        data[\"radius\"][:] = radius\n        self.refresh_bounding_box()\n        return self\n\n    def get_radius(self) -> float:\n        return self.get_radii().max()\n\n    def scale_radii(self, scale_factor: float) -> Self:\n        self.set_radius(scale_factor * self.get_radii())\n        return self\n\n    def set_glow_factor(self, glow_factor: float) -> Self:\n        self.uniforms[\"glow_factor\"] = glow_factor\n        return self\n\n    def get_glow_factor(self) -> float:\n        return self.uniforms[\"glow_factor\"]\n\n    def compute_bounding_box(self) -> Vect3Array:\n        bb = super().compute_bounding_box()\n        radius = self.get_radius()\n        bb[0] += np.full((3,), -radius)\n        bb[2] += np.full((3,), radius)\n        return bb\n\n    def scale(\n        self,\n        scale_factor: float | npt.ArrayLike,\n        scale_radii: bool = True,\n        **kwargs\n    ) -> Self:\n        super().scale(scale_factor, **kwargs)\n        if scale_radii:\n            self.set_radii(scale_factor * self.get_radii())\n        return self\n\n    def make_3d(\n        self,\n        reflectiveness: float = 0.5,\n        gloss: float = 0.1,\n        shadow: float = 0.2\n    ) -> Self:\n        self.set_shading(reflectiveness, gloss, shadow)\n        self.apply_depth_test()\n        return self\n\n\nclass TrueDot(DotCloud):\n    def __init__(self, center: Vect3 = ORIGIN, **kwargs):\n        super().__init__(points=np.array([center]), **kwargs)\n\n\nclass GlowDots(DotCloud):\n    def __init__(\n        self,\n        points: Vect3Array = NULL_POINTS,\n        color: ManimColor = YELLOW,\n        radius: float = DEFAULT_GLOW_DOT_RADIUS,\n        glow_factor: float = 2.0,\n        **kwargs,\n    ):\n        super().__init__(\n            points,\n            color=color,\n            radius=radius,\n            glow_factor=glow_factor,\n            **kwargs,\n        )\n\n\nclass GlowDot(GlowDots):\n    def __init__(self, center: Vect3 = ORIGIN, **kwargs):\n        super().__init__(points=np.array([center]), **kwargs)\n"
  },
  {
    "path": "manimlib/mobject/types/image_mobject.py",
    "content": "from __future__ import annotations\n\nimport numpy as np\nimport moderngl\nfrom PIL import Image\n\nfrom manimlib.constants import DL, DR, UL, UR\nfrom manimlib.mobject.mobject import Mobject\nfrom manimlib.utils.bezier import inverse_interpolate\nfrom manimlib.utils.images import get_full_raster_image_path\nfrom manimlib.utils.iterables import listify\nfrom manimlib.utils.iterables import resize_with_interpolation\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Sequence, Tuple\n    from manimlib.typing import Vect3\n\n\nclass ImageMobject(Mobject):\n    shader_folder: str = \"image\"\n    data_dtype: Sequence[Tuple[str, type, Tuple[int]]] = [\n        ('point', np.float32, (3,)),\n        ('im_coords', np.float32, (2,)),\n        ('opacity', np.float32, (1,)),\n    ]\n    render_primitive: int = moderngl.TRIANGLES\n\n    def __init__(\n        self,\n        filename: str,\n        height: float = 4.0,\n        **kwargs\n    ):\n        self.height = height\n        self.image_path = get_full_raster_image_path(filename)\n        self.image = Image.open(self.image_path)\n        super().__init__(texture_paths={\"Texture\": self.image_path}, **kwargs)\n\n    def init_data(self) -> None:\n        super().init_data(length=6)\n        self.data[\"point\"][:] = [UL, DL, UR, DR, UR, DL]\n        self.data[\"im_coords\"][:] = [(0, 0), (0, 1), (1, 0), (1, 1), (1, 0), (0, 1)]\n        self.data[\"opacity\"][:] = self.opacity\n\n    def init_points(self) -> None:\n        size = self.image.size\n        self.set_width(2 * size[0] / size[1], stretch=True)\n        self.set_height(self.height)\n\n    @Mobject.affects_data\n    def set_opacity(self, opacity: float, recurse: bool = True):\n        self.data[\"opacity\"][:, 0] = resize_with_interpolation(\n            np.array(listify(opacity)),\n            self.get_num_points()\n        )\n        return self\n\n    def set_color(self, color, opacity=None, recurse=None):\n        return self\n\n    def point_to_rgb(self, point: Vect3) -> Vect3:\n        x0, y0 = self.get_corner(UL)[:2]\n        x1, y1 = self.get_corner(DR)[:2]\n        x_alpha = inverse_interpolate(x0, x1, point[0])\n        y_alpha = inverse_interpolate(y0, y1, point[1])\n        if not (0 <= x_alpha <= 1) and (0 <= y_alpha <= 1):\n            # TODO, raise smarter exception\n            raise Exception(\"Cannot sample color from outside an image\")\n\n        pw, ph = self.image.size\n        rgb = self.image.getpixel((\n            int((pw - 1) * x_alpha),\n            int((ph - 1) * y_alpha),\n        ))[:3]\n        return np.array(rgb) / 255\n"
  },
  {
    "path": "manimlib/mobject/types/point_cloud_mobject.py",
    "content": "from __future__ import annotations\n\nimport numpy as np\n\nfrom manimlib.mobject.mobject import Mobject\nfrom manimlib.utils.color import color_gradient\nfrom manimlib.utils.color import color_to_rgba\nfrom manimlib.utils.iterables import resize_with_interpolation\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable\n    from manimlib.typing import ManimColor, Vect3, Vect3Array, Vect4Array, Self\n\n\nclass PMobject(Mobject):\n    def set_points(self, points: Vect3Array):\n        if len(points) == 0:\n            points = np.zeros((0, 3))\n        super().set_points(points)\n        self.resize_points(len(points))\n        return self\n\n    def add_points(\n        self,\n        points: Vect3Array,\n        rgbas: Vect4Array | None = None,\n        color: ManimColor | None = None,\n        opacity: float | None = None\n    ) -> Self:\n        \"\"\"\n        points must be a Nx3 numpy array, as must rgbas if it is not None\n        \"\"\"\n        self.append_points(points)\n        # rgbas array will have been resized with points\n        if color is not None:\n            if opacity is None:\n                opacity = self.data[\"rgba\"][-1, 3]\n            rgbas = np.repeat(\n                [color_to_rgba(color, opacity)],\n                len(points),\n                axis=0\n            )\n        if rgbas is not None:\n            self.data[\"rgba\"][-len(rgbas):] = rgbas\n        return self\n\n    def add_point(self, point: Vect3, rgba=None, color=None, opacity=None) -> Self:\n        rgbas = None if rgba is None else [rgba]\n        self.add_points([point], rgbas, color, opacity)\n        return self\n\n    @Mobject.affects_data\n    def set_color_by_gradient(self, *colors: ManimColor) -> Self:\n        self.data[\"rgba\"][:] = np.array(list(map(\n            color_to_rgba,\n            color_gradient(colors, self.get_num_points())\n        )))\n        return self\n\n    @Mobject.affects_data\n    def match_colors(self, pmobject: PMobject) -> Self:\n        self.data[\"rgba\"][:] = resize_with_interpolation(\n            pmobject.data[\"rgba\"], self.get_num_points()\n        )\n        return self\n\n    @Mobject.affects_data\n    def filter_out(self, condition: Callable[[np.ndarray], bool]) -> Self:\n        for mob in self.family_members_with_points():\n            mob.data = mob.data[~np.apply_along_axis(condition, 1, mob.get_points())]\n        return self\n\n    @Mobject.affects_data\n    def sort_points(self, function: Callable[[Vect3], None] = lambda p: p[0]) -> Self:\n        \"\"\"\n        function is any map from R^3 to R\n        \"\"\"\n        for mob in self.family_members_with_points():\n            indices = np.argsort(\n                np.apply_along_axis(function, 1, mob.get_points())\n            )\n            mob.data[:] = mob.data[indices]\n        return self\n\n    @Mobject.affects_data\n    def ingest_submobjects(self) -> Self:\n        self.data = np.vstack([\n            sm.data for sm in self.get_family()\n        ])\n        return self\n\n    def point_from_proportion(self, alpha: float) -> np.ndarray:\n        index = alpha * (self.get_num_points() - 1)\n        return self.get_points()[int(index)]\n\n    @Mobject.affects_data\n    def pointwise_become_partial(self, pmobject: PMobject, a: float, b: float) -> Self:\n        lower_index = int(a * pmobject.get_num_points())\n        upper_index = int(b * pmobject.get_num_points())\n        self.data = pmobject.data[lower_index:upper_index].copy()\n        return self\n\n\nclass PGroup(PMobject):\n    def __init__(self, *pmobs: PMobject, **kwargs):\n        if not all([isinstance(m, PMobject) for m in pmobs]):\n            raise Exception(\"All submobjects must be of type PMobject\")\n        super().__init__(**kwargs)\n        self.add(*pmobs)\n"
  },
  {
    "path": "manimlib/mobject/types/surface.py",
    "content": "from __future__ import annotations\n\nimport moderngl\nimport numpy as np\nimport trimesh\nimport pywavefront\nimport logging\nfrom pathlib import Path\n\nfrom manimlib.constants import GREY\nfrom manimlib.constants import OUT\nfrom manimlib.mobject.mobject import Mobject\nfrom manimlib.mobject.mobject import Group\nfrom manimlib.utils.bezier import integer_interpolate\nfrom manimlib.utils.bezier import interpolate\nfrom manimlib.utils.bezier import inverse_interpolate\nfrom manimlib.utils.images import get_full_raster_image_path\nfrom manimlib.utils.images import get_full_three_d_model_path\nfrom manimlib.utils.iterables import listify\nfrom manimlib.utils.iterables import resize_with_interpolation\nfrom manimlib.utils.simple_functions import clip\nfrom manimlib.utils.space_ops import normalize_along_axis\nfrom manimlib.utils.space_ops import cross\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable, Iterable, Sequence, Tuple\n\n    from manimlib.camera.camera import Camera\n    from manimlib.typing import ManimColor, Vect3, Vect3Array, Self\n\n\nclass Surface(Mobject):\n    render_primitive: int = moderngl.TRIANGLES\n    shader_folder: str = \"surface\"\n    data_dtype: np.dtype = np.dtype([\n        ('point', np.float32, (3,)),\n        ('d_normal_point', np.float32, (3,)),\n        ('rgba', np.float32, (4,)),\n    ])\n    pointlike_data_keys = ['point', 'd_normal_point']\n\n    def __init__(\n        self,\n        color: ManimColor = GREY,\n        shading: Tuple[float, float, float] = (0.3, 0.2, 0.4),\n        depth_test: bool = True,\n        u_range: Tuple[float, float] = (0.0, 1.0),\n        v_range: Tuple[float, float] = (0.0, 1.0),\n        # Resolution counts number of points sampled, which for\n        # each coordinate is one more than the the number of\n        # rows/columns of approximating squares\n        resolution: Tuple[int, int] = (101, 101),\n        preferred_creation_axis: int = 1,\n        # For du and dv steps.\n        epsilon: float = 1e-3,\n        # Step off the surface to a new point which will\n        # be used to determine the normal direction\n        normal_nudge: float = 1e-3,\n        **kwargs\n    ):\n        self.u_range = u_range\n        self.v_range = v_range\n        self.resolution = resolution\n        self.preferred_creation_axis = preferred_creation_axis\n        self.epsilon = epsilon\n        self.normal_nudge = normal_nudge\n\n        super().__init__(\n            **kwargs,\n            color=color,\n            shading=shading,\n            depth_test=depth_test,\n        )\n        self.compute_triangle_indices()\n\n    def uv_func(self, u: float, v: float) -> tuple[float, float, float]:\n        # To be implemented in subclasses\n        return (u, v, 0.0)\n\n    @Mobject.affects_data\n    def init_points(self):\n        # Get three lists:\n        # - Points generated by pure uv values\n        # - Those generated by values nudged by du\n        # - Those generated by values nudged by dv\n        nu, nv = self.resolution\n        uv_grid = self.get_uv_grid()\n        uv_plus_du = uv_grid.copy()\n        uv_plus_du[:, :, 0] += self.epsilon\n        uv_plus_dv = uv_grid.copy()\n        uv_plus_dv[:, :, 1] += self.epsilon\n\n        points, du_points, dv_points = [\n            np.apply_along_axis(\n                lambda p: self.uv_func(*p), 2, grid\n            ).reshape((nu * nv, self.dim))\n            for grid in (uv_grid, uv_plus_du, uv_plus_dv)\n        ]\n        crosses = cross(du_points - points, dv_points - points)\n        normals = normalize_along_axis(crosses, 1)\n\n        self.set_points(points)\n        self.data['d_normal_point'] = points + self.normal_nudge * normals\n\n    def get_uv_grid(self) -> np.array:\n        \"\"\"\n        Returns an (nu, nv, 2) array of all pairs of u, v values, where\n        (nu, nv) is the resolution\n        \"\"\"\n        nu, nv = self.resolution\n        u_range = np.linspace(*self.u_range, nu)\n        v_range = np.linspace(*self.v_range, nv)\n        U, V = np.meshgrid(u_range, v_range, indexing='ij')\n        return np.stack([U, V], axis=-1)\n\n    def uv_to_point(self, u, v):\n        nu, nv = self.resolution\n        verts_by_uv = np.reshape(self.get_points(), (nu, nv, self.dim))\n\n        alpha1 = clip(inverse_interpolate(*self.u_range[:2], u), 0, 1)\n        alpha2 = clip(inverse_interpolate(*self.v_range[:2], v), 0, 1)\n        scaled_u = alpha1 * (nu - 1)\n        scaled_v = alpha2 * (nv - 1)\n        u_int = int(scaled_u)\n        v_int = int(scaled_v)\n        u_int_plus = min(u_int + 1, nu - 1)\n        v_int_plus = min(v_int + 1, nv - 1)\n\n        a = verts_by_uv[u_int, v_int, :]\n        b = verts_by_uv[u_int, v_int_plus, :]\n        c = verts_by_uv[u_int_plus, v_int, :]\n        d = verts_by_uv[u_int_plus, v_int_plus, :]\n\n        u_res = scaled_u % 1\n        v_res = scaled_v % 1\n        return interpolate(\n            interpolate(a, b, v_res),\n            interpolate(c, d, v_res),\n            u_res\n        )\n\n    def apply_points_function(self, *args, **kwargs) -> Self:\n        super().apply_points_function(*args, **kwargs)\n        self.get_unit_normals()\n        return self\n\n    def compute_triangle_indices(self) -> np.ndarray:\n        # TODO, if there is an event which changes\n        # the resolution of the surface, make sure\n        # this is called.\n        nu, nv = self.resolution\n        if nu == 0 or nv == 0:\n            self.triangle_indices = np.zeros(0, dtype=int)\n            return self.triangle_indices\n        index_grid = np.arange(nu * nv).reshape((nu, nv))\n        indices = np.zeros(6 * (nu - 1) * (nv - 1), dtype=int)\n        indices[0::6] = index_grid[:-1, :-1].flatten()  # Top left\n        indices[1::6] = index_grid[+1:, :-1].flatten()  # Bottom left\n        indices[2::6] = index_grid[:-1, +1:].flatten()  # Top right\n        indices[3::6] = index_grid[:-1, +1:].flatten()  # Top right\n        indices[4::6] = index_grid[+1:, :-1].flatten()  # Bottom left\n        indices[5::6] = index_grid[+1:, +1:].flatten()  # Bottom right\n        self.triangle_indices = indices\n        return self.triangle_indices\n\n    def get_triangle_indices(self) -> np.ndarray:\n        return self.triangle_indices\n\n    def get_unit_normals(self) -> Vect3Array:\n        # TOOD, I could try a more resiliant way to compute this using the neighboring grid values\n        return normalize_along_axis(self.data['d_normal_point'] - self.data['point'], 1)\n\n    @Mobject.affects_data\n    def pointwise_become_partial(\n        self,\n        smobject: \"Surface\",\n        a: float,\n        b: float,\n        axis: int | None = None\n    ) -> Self:\n        assert isinstance(smobject, Surface)\n        if axis is None:\n            axis = self.preferred_creation_axis\n        if a <= 0 and b >= 1:\n            self.match_points(smobject)\n            return self\n\n        nu, nv = smobject.resolution\n        self.data['point'][:] = self.get_partial_points_array(\n            smobject.data['point'], a, b,\n            (nu, nv, 3),\n            axis=axis\n        )\n        return self\n\n    def get_partial_points_array(\n        self,\n        points: Vect3Array,\n        a: float,\n        b: float,\n        resolution: Sequence[int],\n        axis: int\n    ) -> Vect3Array:\n        if len(points) == 0:\n            return points\n        nu, nv = resolution[:2]\n        points = points.reshape(resolution).copy()\n        max_index = resolution[axis] - 1\n        lower_index, lower_residue = integer_interpolate(0, max_index, a)\n        upper_index, upper_residue = integer_interpolate(0, max_index, b)\n        if axis == 0:\n            points[:lower_index] = interpolate(\n                points[lower_index],\n                points[lower_index + 1],\n                lower_residue\n            )\n            points[upper_index + 1:] = interpolate(\n                points[upper_index],\n                points[upper_index + 1],\n                upper_residue\n            )\n        else:\n            shape = (nu, 1, resolution[2])\n            points[:, :lower_index] = interpolate(\n                points[:, lower_index],\n                points[:, lower_index + 1],\n                lower_residue\n            ).reshape(shape)\n            points[:, upper_index + 1:] = interpolate(\n                points[:, upper_index],\n                points[:, upper_index + 1],\n                upper_residue\n            ).reshape(shape)\n        return points.reshape((nu * nv, *resolution[2:]))\n\n    @Mobject.affects_data\n    def sort_faces_back_to_front(self, vect: Vect3 = OUT) -> Self:\n        tri_is = self.triangle_indices\n        points = self.get_points()\n\n        dots = np.dot(points[tri_is[::3]], vect.T)\n        indices = np.argsort(dots)\n        for k in range(3):\n            tri_is[k::3] = tri_is[k::3][indices]\n        return self\n\n    def always_sort_to_camera(self, camera: Camera) -> Self:\n        def updater(surface: Surface):\n            vect = camera.get_location() - surface.get_center()\n            surface.sort_faces_back_to_front(vect)\n        self.add_updater(updater)\n        return self\n\n    def color_by_uv_function(self, uv_to_color: Callable[[Vect2], Color]):\n        uv_grid = self.get_uv_grid()\n        self.set_rgba_array_by_color([\n            uv_to_color(u, v)\n            for u, v in uv_grid.reshape(-1, 2)\n        ])\n        return self\n\n    def get_shader_vert_indices(self) -> np.ndarray:\n        return self.get_triangle_indices()\n\n\nclass ParametricSurface(Surface):\n    def __init__(\n        self,\n        uv_func: Callable[[float, float], Iterable[float]],\n        u_range: tuple[float, float] = (0, 1),\n        v_range: tuple[float, float] = (0, 1),\n        **kwargs\n    ):\n        self.passed_uv_func = uv_func\n        super().__init__(u_range=u_range, v_range=v_range, **kwargs)\n\n    def uv_func(self, u, v):\n        return self.passed_uv_func(u, v)\n\n\nclass SGroup(Surface):\n    def __init__(\n        self,\n        *parametric_surfaces: Surface,\n        **kwargs\n    ):\n        super().__init__(resolution=(0, 0), **kwargs)\n        self.add(*parametric_surfaces)\n\n    def init_points(self):\n        pass  # Needed?\n\n\nclass TexturedSurface(Surface):\n    shader_folder: str = \"textured_surface\"\n    data_dtype: Sequence[Tuple[str, type, Tuple[int]]] = [\n        ('point', np.float32, (3,)),\n        ('d_normal_point', np.float32, (3,)),\n        ('im_coords', np.float32, (2,)),\n        ('opacity', np.float32, (1,)),\n    ]\n\n    def __init__(\n        self,\n        uv_surface: Surface,\n        image_file: str,\n        dark_image_file: str | None = None,\n        **kwargs\n    ):\n        if not isinstance(uv_surface, Surface):\n            raise Exception(\"uv_surface must be of type Surface\")\n        # Set texture information\n        if dark_image_file is None:\n            dark_image_file = image_file\n            self.num_textures = 1\n        else:\n            self.num_textures = 2\n\n        texture_paths = {\n            \"LightTexture\": get_full_raster_image_path(image_file),\n            \"DarkTexture\": get_full_raster_image_path(dark_image_file),\n        }\n\n        self.uv_surface = uv_surface\n        self.uv_func = uv_surface.uv_func\n        self.u_range: Tuple[float, float] = uv_surface.u_range\n        self.v_range: Tuple[float, float] = uv_surface.v_range\n        self.resolution: Tuple[int, int] = uv_surface.resolution\n        super().__init__(\n            texture_paths=texture_paths,\n            shading=tuple(uv_surface.shading),\n            **kwargs\n        )\n\n    @Mobject.affects_data\n    def init_points(self):\n        surf = self.uv_surface\n        nu, nv = surf.resolution\n        self.resize_points(surf.get_num_points())\n        self.resolution = surf.resolution\n        self.data['point'][:] = surf.data['point']\n        self.data['d_normal_point'][:] = surf.data['d_normal_point']\n        self.data['opacity'][:, 0] = surf.data[\"rgba\"][:, 3]\n        self.data[\"im_coords\"] = np.array([\n            [u, v]\n            for u in np.linspace(0, 1, nu)\n            for v in np.linspace(1, 0, nv)  # Reverse y-direction\n        ])\n\n    @Mobject.affects_data\n    def set_image_coords_by_uv_func(self, uv_func) -> Self:\n        \"\"\"\n        uv_func takes in a pair (u, v), and returns a new pair (u', v') used\n        for coordinates when reading from the texture\n        \"\"\"\n        nu, nv = self.uv_surface.resolution\n        self.data[\"im_coords\"][:] = np.array([\n            uv_func(u, v)\n            for u in np.linspace(0, 1, nu)\n            for v in np.linspace(1, 0, nv)  # Reverse y-direction\n        ])\n        return self\n\n    def init_uniforms(self):\n        super().init_uniforms()\n        self.uniforms[\"num_textures\"] = self.num_textures\n\n    @Mobject.affects_data\n    def set_opacity(self, opacity: float | Iterable[float], recurse=True) -> Self:\n        op_arr = np.array(listify(opacity))\n        self.data[\"opacity\"][:, 0] = resize_with_interpolation(op_arr, len(self.data))\n        return self\n\n    def set_color(\n        self,\n        color: ManimColor | Iterable[ManimColor] | None,\n        opacity: float | Iterable[float] | None = None,\n        recurse: bool = True\n    ) -> Self:\n        if opacity is not None:\n            self.set_opacity(opacity)\n        return self\n\n    def pointwise_become_partial(\n        self,\n        tsmobject: \"TexturedSurface\",\n        a: float,\n        b: float,\n        axis: int | None = None\n    ) -> Self:\n        if axis is None:\n            axis = self.preferred_creation_axis\n        super().pointwise_become_partial(tsmobject, a, b, axis)\n        im_coords = self.data[\"im_coords\"]\n        im_coords[:] = tsmobject.data[\"im_coords\"]\n        if a <= 0 and b >= 1:\n            return self\n        nu, nv = tsmobject.resolution\n        im_coords[:] = self.get_partial_points_array(\n            im_coords, a, b, (nu, nv, 2), axis\n        )\n        return self\n\n\nclass TexturedGeometry(TexturedSurface):\n    def __init__(self, geometry: trimesh.base.Trimesh, texture_file: str, **kwargs):\n        self.num_textures = 1\n        self.geometry = geometry\n        self.texture_file = texture_file\n        self.triangle_indices = geometry.faces.flatten()\n        Mobject.__init__(\n            self,\n            texture_paths={\"LightTexture\": get_full_raster_image_path(texture_file)}\n        )\n\n    def init_points(self):\n        points = self.geometry.vertices\n        uv = np.array(self.geometry.visual.uv)\n        uv[:, 1] = 1.0 - uv[:, 1]\n\n        v0 = points[self.triangle_indices[0::3]]\n        v1 = points[self.triangle_indices[0::3]]\n        v2 = points[self.triangle_indices[0::3]]\n        normals = points\n        epsilon = 1e-5 * 0\n\n        self.set_points(points)\n        self.data[\"d_normal_point\"] = points + epsilon * normals\n        self.data[\"im_coords\"] = uv\n        self.data[\"opacity\"] = self.opacity\n\n\nclass ThreeDModel(Group):\n    def __init__(self, obj_file: str, height=3):\n        super().__init__()\n        obj_file = get_full_three_d_model_path(obj_file)\n\n        default_texture = Path(Path(obj_file).parent, \"texture.png\")\n        if not default_texture.exists():\n            default_texture = get_full_raster_image_path(\"White.png\")\n\n        texture_files = self.get_textures_from_mtl(obj_file)\n        mesh = trimesh.load(obj_file)\n\n        if isinstance(mesh, trimesh.Scene):\n            self.add(*(\n                TexturedGeometry(geom, texture or default_texture)\n                for geom, texture in zip(mesh.geometry.values(), texture_files.values())\n            ))\n        elif isinstance(mesh, trimesh.Geometry):\n            # TODO\n            self.add(TexturedGeometry(mesh, default_texture))\n\n        self.apply_depth_test()\n        self.set_height(height)\n        self.center()\n\n    def get_textures_from_mtl(self, obj_filepath, suppress_warnings=True):\n        \"\"\"\n        Load an OBJ file and extract all texture filenames from its MTL file.\n\n        Returns:\n            dict: {material_name: texture_filepath}\n        \"\"\"\n\n        # Suppress pywavefront warnings if desired\n        if suppress_warnings:\n            logging.getLogger('pywavefront').setLevel(logging.ERROR)\n\n        # Load the OBJ file (automatically loads MTL)\n        obj_scene = pywavefront.Wavefront(obj_filepath, collect_faces=True)\n\n        textures = {}\n\n        # Iterate through materials\n        for material_name, material in obj_scene.materials.items():\n            if material.texture:\n                textures[material_name] = material.texture.path\n            else:\n                textures[material_name] = None\n\n        return textures\n"
  },
  {
    "path": "manimlib/mobject/types/vectorized_mobject.py",
    "content": "from __future__ import annotations\n\nfrom functools import wraps\n\nimport numpy as np\n\nfrom manimlib.constants import GREY_A, GREY_C, GREY_E\nfrom manimlib.constants import DEFAULT_VMOBJECT_FILL_COLOR, DEFAULT_VMOBJECT_STROKE_COLOR\nfrom manimlib.constants import BLACK\nfrom manimlib.constants import DEFAULT_STROKE_WIDTH\nfrom manimlib.constants import DEG\nfrom manimlib.constants import ORIGIN, OUT\nfrom manimlib.constants import PI\nfrom manimlib.constants import TAU\nfrom manimlib.mobject.mobject import Mobject\nfrom manimlib.mobject.mobject import Group\nfrom manimlib.mobject.mobject import Point\nfrom manimlib.utils.bezier import bezier\nfrom manimlib.utils.bezier import get_quadratic_approximation_of_cubic\nfrom manimlib.utils.bezier import approx_smooth_quadratic_bezier_handles\nfrom manimlib.utils.bezier import smooth_quadratic_path\nfrom manimlib.utils.bezier import interpolate\nfrom manimlib.utils.bezier import integer_interpolate\nfrom manimlib.utils.bezier import inverse_interpolate\nfrom manimlib.utils.bezier import find_intersection\nfrom manimlib.utils.bezier import outer_interpolate\nfrom manimlib.utils.bezier import partial_quadratic_bezier_points\nfrom manimlib.utils.bezier import quadratic_bezier_points_for_arc\nfrom manimlib.utils.color import color_gradient\nfrom manimlib.utils.color import rgb_to_hex\nfrom manimlib.utils.iterables import make_even\nfrom manimlib.utils.iterables import resize_array\nfrom manimlib.utils.iterables import resize_with_interpolation\nfrom manimlib.utils.iterables import resize_preserving_order\nfrom manimlib.utils.space_ops import angle_between_vectors\nfrom manimlib.utils.space_ops import cross2d\nfrom manimlib.utils.space_ops import earclip_triangulation\nfrom manimlib.utils.space_ops import get_norm\nfrom manimlib.utils.space_ops import get_unit_normal\nfrom manimlib.utils.space_ops import line_intersects_path\nfrom manimlib.utils.space_ops import midpoint\nfrom manimlib.utils.space_ops import rotation_between_vectors\nfrom manimlib.utils.space_ops import rotation_matrix_transpose\nfrom manimlib.utils.space_ops import poly_line_length\nfrom manimlib.utils.space_ops import z_to_vector\nfrom manimlib.shader_wrapper import VShaderWrapper\n\nfrom typing import TYPE_CHECKING\nfrom typing import Generic, TypeVar, Iterable\nSubVmobjectType = TypeVar('SubVmobjectType', bound='VMobject')\n\nif TYPE_CHECKING:\n    from typing import Callable, Tuple, Any, Optional\n    from manimlib.typing import ManimColor, Vect3, Vect4, Vect3Array, Self\n    from moderngl.context import Context\n\n\nclass VMobject(Mobject):\n    data_dtype: np.dtype = np.dtype([\n        ('point', np.float32, (3,)),\n        ('stroke_rgba', np.float32, (4,)),\n        ('stroke_width', np.float32, (1,)),\n        ('joint_angle', np.float32, (1,)),\n        ('fill_rgba', np.float32, (4,)),\n        ('base_normal', np.float32, (3,)),  # Base points and unit normal vectors are interleaved in this array\n        ('fill_border_width', np.float32, (1,)),\n    ])\n    pre_function_handle_to_anchor_scale_factor: float = 0.01\n    make_smooth_after_applying_functions: bool = False\n    # TODO, do we care about accounting for varying zoom levels?\n    tolerance_for_point_equality: float = 1e-8\n    joint_type_map: dict = {\n        \"no_joint\": 0,\n        \"auto\": 1,\n        \"bevel\": 2,\n        \"miter\": 3,\n    }\n\n    def __init__(\n        self,\n        color: ManimColor = None,  # If set, this will override stroke_color and fill_color\n        fill_color: ManimColor = None,\n        fill_opacity: float | Iterable[float] | None = 0.0,\n        stroke_color: ManimColor = None,\n        stroke_opacity: float | Iterable[float] | None = 1.0,\n        stroke_width: float | Iterable[float] | None = DEFAULT_STROKE_WIDTH,\n        stroke_behind: bool = False,\n        background_image_file: str | None = None,\n        long_lines: bool = False,\n        # Could also be \"no_joint\", \"bevel\", \"miter\"\n        joint_type: str = \"auto\",\n        flat_stroke: bool = False,\n        scale_stroke_with_zoom: bool = False,\n        use_simple_quadratic_approx: bool = False,\n        # Measured in pixel widths\n        anti_alias_width: float = 1.5,\n        fill_border_width: float = 0.0,\n        **kwargs\n    ):\n        self.fill_color = fill_color or color or DEFAULT_VMOBJECT_FILL_COLOR\n        self.fill_opacity = fill_opacity\n        self.stroke_color = stroke_color or color or DEFAULT_VMOBJECT_STROKE_COLOR\n        self.stroke_opacity = stroke_opacity\n        self.stroke_width = stroke_width\n        self.stroke_behind = stroke_behind\n        self.background_image_file = background_image_file\n        self.long_lines = long_lines\n        self.joint_type = joint_type\n        self.flat_stroke = flat_stroke\n        self.scale_stroke_with_zoom = scale_stroke_with_zoom\n        self.use_simple_quadratic_approx = use_simple_quadratic_approx\n        self.anti_alias_width = anti_alias_width\n        self.fill_border_width = fill_border_width\n\n        self.needs_new_joint_angles = True\n        self.needs_new_unit_normal = True\n        self.subpath_end_indices = None\n        self.outer_vert_indices = np.zeros(0, dtype=int)\n\n        super().__init__(**kwargs)\n\n    def get_group_class(self):\n        return VGroup\n\n    def init_uniforms(self):\n        super().init_uniforms()\n        self.uniforms.update(\n            anti_alias_width=self.anti_alias_width,\n            joint_type=self.joint_type_map[self.joint_type],\n            flat_stroke=float(self.flat_stroke),\n            scale_stroke_with_zoom=float(self.scale_stroke_with_zoom)\n        )\n\n    def add(self, *vmobjects: VMobject) -> Self:\n        if not all((isinstance(m, VMobject) for m in vmobjects)):\n            raise Exception(\"All submobjects must be of type VMobject\")\n        return super().add(*vmobjects)\n\n    # Colors\n    def init_colors(self):\n        self.set_stroke(\n            color=self.stroke_color,\n            width=self.stroke_width,\n            opacity=self.stroke_opacity,\n            behind=self.stroke_behind,\n        )\n        self.set_fill(\n            color=self.fill_color,\n            opacity=self.fill_opacity,\n            border_width=self.fill_border_width,\n        )\n        self.set_shading(*self.shading)\n        self.set_flat_stroke(self.flat_stroke)\n        self.color = self.get_color()\n        return self\n\n    def set_fill(\n        self,\n        color: ManimColor | Iterable[ManimColor] = None,\n        opacity: float | Iterable[float] | None = None,\n        border_width: float | None = None,\n        recurse: bool = True\n    ) -> Self:\n        self.set_rgba_array_by_color(color, opacity, 'fill_rgba', recurse)\n        if border_width is not None:\n            self.border_width = border_width\n            for mob in self.get_family(recurse):\n                data = mob.data if mob.has_points() > 0 else mob._data_defaults\n                data[\"fill_border_width\"] = border_width\n        return self\n\n    def set_stroke(\n        self,\n        color: ManimColor | Iterable[ManimColor] = None,\n        width: float | Iterable[float] | None = None,\n        opacity: float | Iterable[float] | None = None,\n        behind: bool | None = None,\n        flat: bool | None = None,\n        recurse: bool = True\n    ) -> Self:\n        self.set_rgba_array_by_color(color, opacity, 'stroke_rgba', recurse)\n\n        if width is not None:\n            for mob in self.get_family(recurse):\n                data = mob.data if mob.get_num_points() > 0 else mob._data_defaults\n                if isinstance(width, (float, int, np.floating)):\n                    data['stroke_width'][:, 0] = width\n                else:\n                    data['stroke_width'][:, 0] = resize_with_interpolation(\n                        np.array(width), len(data)\n                    ).flatten()\n\n        if behind is not None:\n            for mob in self.get_family(recurse):\n                if mob.stroke_behind != behind:\n                    mob.stroke_behind = behind\n                    mob.refresh_shader_wrapper_id()\n\n        if flat is not None:\n            self.set_flat_stroke(flat)\n\n        return self\n\n    def set_backstroke(\n        self,\n        color: ManimColor | Iterable[ManimColor] = BLACK,\n        width: float | Iterable[float] = 3,\n    ) -> Self:\n        self.set_stroke(color, width, behind=True)\n        return self\n\n    @Mobject.affects_family_data\n    def set_style(\n        self,\n        fill_color: ManimColor | Iterable[ManimColor] | None = None,\n        fill_opacity: float | Iterable[float] | None = None,\n        fill_rgba: Vect4 | None = None,\n        fill_border_width: float | None = None,\n        stroke_color: ManimColor | Iterable[ManimColor] | None = None,\n        stroke_opacity: float | Iterable[float] | None = None,\n        stroke_rgba: Vect4 | None = None,\n        stroke_width: float | Iterable[float] | None = None,\n        stroke_behind: bool | None = None,\n        flat_stroke: Optional[bool] = None,\n        shading: Tuple[float, float, float] | None = None,\n        recurse: bool = True\n    ) -> Self:\n        for mob in self.get_family(recurse):\n            if fill_rgba is not None:\n                mob.data['fill_rgba'][:] = resize_with_interpolation(fill_rgba, len(mob.data['fill_rgba']))\n            else:\n                mob.set_fill(\n                    color=fill_color,\n                    opacity=fill_opacity,\n                    border_width=fill_border_width,\n                    recurse=False\n                )\n\n            if stroke_rgba is not None:\n                mob.data['stroke_rgba'][:] = resize_with_interpolation(stroke_rgba, len(mob.data['stroke_rgba']))\n                mob.set_stroke(\n                    width=stroke_width,\n                    behind=stroke_behind,\n                    flat=flat_stroke,\n                    recurse=False,\n                )\n            else:\n                mob.set_stroke(\n                    color=stroke_color,\n                    width=stroke_width,\n                    opacity=stroke_opacity,\n                    flat=flat_stroke,\n                    behind=stroke_behind,\n                    recurse=False,\n                )\n\n            if shading is not None:\n                mob.set_shading(*shading, recurse=False)\n        return self\n\n    def get_style(self) -> dict[str, Any]:\n        data = self.data if self.get_num_points() > 0 else self._data_defaults\n        return {\n            \"fill_rgba\": data['fill_rgba'].copy(),\n            \"fill_border_width\": data['fill_border_width'].copy(),\n            \"stroke_rgba\": data['stroke_rgba'].copy(),\n            \"stroke_width\": data['stroke_width'].copy(),\n            \"stroke_behind\": self.stroke_behind,\n            \"flat_stroke\": self.get_flat_stroke(),\n            \"shading\": self.get_shading(),\n        }\n\n    def match_style(self, vmobject: VMobject, recurse: bool = True) -> Self:\n        self.set_style(**vmobject.get_style(), recurse=False)\n        if recurse:\n            # Does its best to match up submobject lists, and\n            # match styles accordingly\n            submobs1, submobs2 = self.submobjects, vmobject.submobjects\n            if len(submobs1) == 0:\n                return self\n            elif len(submobs2) == 0:\n                submobs2 = [vmobject]\n            for sm1, sm2 in zip(*make_even(submobs1, submobs2)):\n                sm1.match_style(sm2)\n        return self\n\n    def set_color(\n        self,\n        color: ManimColor | Iterable[ManimColor] | None,\n        opacity: float | Iterable[float] | None = None,\n        recurse: bool = True\n    ) -> Self:\n        self.set_fill(color, opacity=opacity, recurse=recurse)\n        self.set_stroke(color, opacity=opacity, recurse=recurse)\n        return self\n\n    def set_opacity(\n        self,\n        opacity: float | Iterable[float] | None,\n        recurse: bool = True\n    ) -> Self:\n        self.set_fill(opacity=opacity, recurse=recurse)\n        self.set_stroke(opacity=opacity, recurse=recurse)\n        return self\n\n    def set_color_by_proportion(self, prop_to_color: Callable[[float], Color]) -> Self:\n        colors = list(map(prop_to_color, np.linspace(0, 1, self.get_num_points())))\n        self.set_stroke(color=colors)\n        return self\n\n    def set_anti_alias_width(self, anti_alias_width: float, recurse: bool = True) -> Self:\n        self.set_uniform(recurse, anti_alias_width=anti_alias_width)\n        return self\n\n    def fade(self, darkness: float = 0.5, recurse: bool = True) -> Self:\n        mobs = self.get_family() if recurse else [self]\n        for mob in mobs:\n            factor = 1.0 - darkness\n            mob.set_fill(\n                opacity=factor * mob.get_fill_opacity(),\n                recurse=False,\n            )\n            mob.set_stroke(\n                opacity=factor * mob.get_stroke_opacity(),\n                recurse=False,\n            )\n        return self\n\n    def get_fill_colors(self) -> list[str]:\n        return [\n            rgb_to_hex(rgba[:3])\n            for rgba in self.data['fill_rgba']\n        ]\n\n    def get_fill_opacities(self) -> np.ndarray:\n        return self.data['fill_rgba'][:, 3]\n\n    def get_stroke_colors(self) -> list[str]:\n        return [\n            rgb_to_hex(rgba[:3])\n            for rgba in self.data['stroke_rgba']\n        ]\n\n    def get_stroke_opacities(self) -> np.ndarray:\n        return self.data['stroke_rgba'][:, 3]\n\n    def get_stroke_widths(self) -> np.ndarray:\n        return self.data['stroke_width'][:, 0]\n\n    # TODO, it's weird for these to return the first of various lists\n    # rather than the full information\n    def get_fill_color(self) -> str:\n        \"\"\"\n        If there are multiple colors (for gradient)\n        this returns the first one\n        \"\"\"\n        data = self.data if self.has_points() else self._data_defaults\n        return rgb_to_hex(data[\"fill_rgba\"][0, :3])\n\n    def get_fill_opacity(self) -> float:\n        \"\"\"\n        If there are multiple opacities, this returns the\n        first\n        \"\"\"\n        data = self.data if self.has_points() else self._data_defaults\n        return data[\"fill_rgba\"][0, 3]\n\n    def get_stroke_color(self) -> str:\n        data = self.data if self.has_points() else self._data_defaults\n        return rgb_to_hex(data[\"stroke_rgba\"][0, :3])\n\n    def get_stroke_width(self) -> float:\n        data = self.data if self.has_points() else self._data_defaults\n        return data[\"stroke_width\"][0, 0]\n\n    def get_stroke_opacity(self) -> float:\n        data = self.data if self.has_points() else self._data_defaults\n        return data[\"stroke_rgba\"][0, 3]\n\n    def get_color(self) -> str:\n        if self.has_fill():\n            return self.get_fill_color()\n        return self.get_stroke_color()\n\n    def get_anti_alias_width(self):\n        return self.uniforms[\"anti_alias_width\"]\n\n    def has_stroke(self) -> bool:\n        data = self.data if len(self.data) > 0 else self._data_defaults\n        return any(data['stroke_width']) and any(data['stroke_rgba'][:, 3])\n\n    def has_fill(self) -> bool:\n        data = self.data if len(self.data) > 0 else self._data_defaults\n        return any(data['fill_rgba'][:, 3])\n\n    def get_opacity(self) -> float:\n        if self.has_fill():\n            return self.get_fill_opacity()\n        return self.get_stroke_opacity()\n\n    def set_flat_stroke(self, flat_stroke: bool = True, recurse: bool = True) -> Self:\n        self.set_uniform(recurse, flat_stroke=float(flat_stroke))\n        return self\n\n    def get_flat_stroke(self) -> bool:\n        return self.uniforms[\"flat_stroke\"] == 1.0\n\n    def set_scale_stroke_with_zoom(self, scale_stroke_with_zoom: bool = True, recurse: bool = True) -> Self:\n        self.set_uniform(recurse, scale_stroke_with_zoom=float(scale_stroke_with_zoom))\n        pass\n\n    def get_scale_stroke_with_zoom(self) -> bool:\n        return self.uniforms[\"flat_stroke\"] == 1.0\n\n    def set_joint_type(self, joint_type: str, recurse: bool = True) -> Self:\n        for mob in self.get_family(recurse):\n            mob.uniforms[\"joint_type\"] = self.joint_type_map[joint_type]\n        return self\n\n    def get_joint_type(self) -> float:\n        return self.uniforms[\"joint_type\"]\n\n    def apply_depth_test(\n        self,\n        anti_alias_width: float = 0,\n        recurse: bool = True\n    ) -> Self:\n        super().apply_depth_test(recurse)\n        self.set_anti_alias_width(anti_alias_width)\n        return self\n\n    def deactivate_depth_test(\n        self,\n        anti_alias_width: float = 1.0,\n        recurse: bool = True\n    ) -> Self:\n        super().deactivate_depth_test(recurse)\n        self.set_anti_alias_width(anti_alias_width)\n        return self\n\n    def use_winding_fill(self, value: bool = True, recurse: bool = True) -> Self:\n        # Only keeping this here because some old scene call it\n        return self\n\n    # Points\n    def set_anchors_and_handles(\n        self,\n        anchors: Vect3Array,\n        handles: Vect3Array,\n    ) -> Self:\n        if len(anchors) == 0:\n            self.clear_points()\n            return self\n        assert len(anchors) == len(handles) + 1\n        points = resize_array(self.get_points(), 2 * len(anchors) - 1)\n        points[0::2] = anchors\n        points[1::2] = handles\n        self.set_points(points)\n        return self\n\n    def start_new_path(self, point: Vect3) -> Self:\n        # Path ends are signaled by a handle point sitting directly\n        # on top of the previous anchor\n        if self.has_points():\n            self.append_points([self.get_last_point(), point])\n        else:\n            self.set_points([point])\n        return self\n\n    def add_cubic_bezier_curve(\n        self,\n        anchor1: Vect3,\n        handle1: Vect3,\n        handle2: Vect3,\n        anchor2: Vect3\n    ) -> Self:\n        self.start_new_path(anchor1)\n        self.add_cubic_bezier_curve_to(handle1, handle2, anchor2)\n        return self\n\n    def add_cubic_bezier_curve_to(\n        self,\n        handle1: Vect3,\n        handle2: Vect3,\n        anchor: Vect3,\n    ) -> Self:\n        \"\"\"\n        Add cubic bezier curve to the path.\n        \"\"\"\n        self.throw_error_if_no_points()\n        last = self.get_last_point()\n        # Note, this assumes all points are on the xy-plane\n        v1 = handle1 - last\n        v2 = anchor - handle2\n        angle = angle_between_vectors(v1, v2)\n        if self.use_simple_quadratic_approx and angle < 45 * DEG:\n            quad_approx = [last, find_intersection(last, v1, anchor, -v2), anchor]\n        else:\n            quad_approx = get_quadratic_approximation_of_cubic(\n                last, handle1, handle2, anchor\n            )\n            if self.consider_points_equal(quad_approx[3], quad_approx[4]):\n                # Avoid degenerate handles (duplicate points) to prevent visual bug\n                quad_approx[3] = midpoint(*quad_approx[2:4])\n        if self.consider_points_equal(quad_approx[1], last):\n            # This is to prevent subpaths from accidentally being marked closed\n            quad_approx[1] = midpoint(*quad_approx[1:3])\n        self.append_points(quad_approx[1:])\n        return self\n\n    def add_quadratic_bezier_curve_to(self, handle: Vect3, anchor: Vect3, allow_null_curve=True) -> Self:\n        self.throw_error_if_no_points()\n        last_point = self.get_last_point()\n        if not allow_null_curve and self.consider_points_equal(last_point, anchor):\n            return self\n        if self.consider_points_equal(handle, last_point):\n            # This is to prevent subpaths from accidentally being marked closed\n            handle = midpoint(handle, anchor)\n        self.append_points([handle, anchor])\n        return self\n\n    def add_line_to(self, point: Vect3, allow_null_line: bool = True) -> Self:\n        self.throw_error_if_no_points()\n        last_point = self.get_last_point()\n        if not allow_null_line and self.consider_points_equal(last_point, point):\n            return self\n        alphas = np.linspace(0, 1, 5 if self.long_lines else 3)\n        self.append_points(outer_interpolate(last_point, point, alphas[1:]))\n        return self\n\n    def add_smooth_curve_to(self, point: Vect3) -> Self:\n        if self.has_new_path_started():\n            self.add_line_to(point)\n        else:\n            self.throw_error_if_no_points()\n            new_handle = self.get_reflection_of_last_handle()\n            self.add_quadratic_bezier_curve_to(new_handle, point)\n        return self\n\n    def add_smooth_cubic_curve_to(self, handle: Vect3, point: Vect3) -> Self:\n        self.throw_error_if_no_points()\n        if self.get_num_points() == 1:\n            new_handle = handle\n        else:\n            new_handle = self.get_reflection_of_last_handle()\n        self.add_cubic_bezier_curve_to(new_handle, handle, point)\n        return self\n\n    def add_arc_to(self, point: Vect3, angle: float, n_components: int | None = None, threshold: float = 1e-3) -> Self:\n        self.throw_error_if_no_points()\n        if abs(angle) < threshold:\n            self.add_line_to(point)\n            return self\n\n        # Assign default value for n_components\n        if n_components is None:\n            n_components = int(np.ceil(8 * abs(angle) / TAU))\n\n        arc_points = quadratic_bezier_points_for_arc(angle, n_components)\n        target_vect = point - self.get_end()\n        curr_vect = arc_points[-1] - arc_points[0]\n\n        arc_points = arc_points @ rotation_between_vectors(curr_vect, target_vect).T\n        arc_points *= get_norm(target_vect) / get_norm(curr_vect)\n        arc_points += (self.get_end() - arc_points[0])\n        self.append_points(arc_points[1:])\n        return self\n\n    def has_new_path_started(self) -> bool:\n        points = self.get_points()\n        if len(points) == 0:\n            return False\n        elif len(points) == 1:\n            return True\n        return self.consider_points_equal(points[-3], points[-2])\n\n    def get_last_point(self) -> Vect3:\n        return self.get_points()[-1]\n\n    def get_reflection_of_last_handle(self) -> Vect3:\n        points = self.get_points()\n        return 2 * points[-1] - points[-2]\n\n    def close_path(self, smooth: bool = False) -> Self:\n        if self.is_closed():\n            return self\n        ends = self.get_subpath_end_indices()\n        last_path_start = self.get_points()[0 if len(ends) == 1 else ends[-2] + 2]\n        if smooth:\n            self.add_smooth_curve_to(last_path_start)\n        else:\n            self.add_line_to(last_path_start)\n        return self\n\n    def is_closed(self) -> bool:\n        points = self.get_points()\n        ends = self.get_subpath_end_indices()\n        last_path_start = points[0 if len(ends) == 1 else ends[-2] + 2]\n        return self.consider_points_equal(last_path_start, points[-1])\n\n    def subdivide_curves_by_condition(\n        self,\n        tuple_to_subdivisions: Callable,\n        recurse: bool = True\n    ) -> Self:\n        for vmob in self.get_family(recurse):\n            if not vmob.has_points():\n                continue\n            new_points = [vmob.get_points()[0]]\n            for tup in vmob.get_bezier_tuples():\n                n_divisions = tuple_to_subdivisions(*tup)\n                if n_divisions > 0:\n                    alphas = np.linspace(0, 1, n_divisions + 2)\n                    new_points.extend([\n                        partial_quadratic_bezier_points(tup, a1, a2)[1:]\n                        for a1, a2 in zip(alphas, alphas[1:])\n                    ])\n                else:\n                    new_points.append(tup[1:])\n            vmob.set_points(np.vstack(new_points))\n        return self\n\n    def subdivide_sharp_curves(\n        self,\n        angle_threshold: float = 30 * DEG,\n        recurse: bool = True\n    ) -> Self:\n        def tuple_to_subdivisions(b0, b1, b2):\n            angle = angle_between_vectors(b1 - b0, b2 - b1)\n            return int(angle / angle_threshold)\n\n        self.subdivide_curves_by_condition(tuple_to_subdivisions, recurse)\n        return self\n\n    def subdivide_intersections(self, recurse: bool = True, n_subdivisions: int = 1) -> Self:\n        path = self.get_anchors()\n        def tuple_to_subdivisions(b0, b1, b2):\n            if line_intersects_path(b0, b1, path):\n                return n_subdivisions\n            return 0\n\n        self.subdivide_curves_by_condition(tuple_to_subdivisions, recurse)\n        return self\n\n    def add_points_as_corners(self, points: Iterable[Vect3]) -> Self:\n        for point in points:\n            self.add_line_to(point)\n        return self\n\n    def set_points_as_corners(self, points: Iterable[Vect3]) -> Self:\n        anchors = np.array(points)\n        handles = 0.5 * (anchors[:-1] + anchors[1:])\n        self.set_anchors_and_handles(anchors, handles)\n        return self\n\n    def set_points_smoothly(\n        self,\n        points: Iterable[Vect3],\n        approx: bool = True\n    ) -> Self:\n        self.set_points_as_corners(points)\n        self.make_smooth(approx=approx)\n        return self\n\n    def is_smooth(self, angle_tol=1 * DEG) -> bool:\n        angles = np.abs(self.get_joint_angles()[0::2])\n        return (angles < angle_tol).all()\n\n    def change_anchor_mode(self, mode: str) -> Self:\n        assert mode in (\"jagged\", \"approx_smooth\", \"true_smooth\")\n        if self.get_num_points() == 0:\n            return self\n        subpaths = self.get_subpaths()\n        self.clear_points()\n        for subpath in subpaths:\n            anchors = subpath[::2]\n            new_subpath = np.array(subpath)\n            if mode == \"jagged\":\n                new_subpath[1::2] = 0.5 * (anchors[:-1] + anchors[1:])\n            elif mode == \"approx_smooth\":\n                new_subpath[1::2] = approx_smooth_quadratic_bezier_handles(anchors)\n            elif mode == \"true_smooth\":\n                new_subpath = smooth_quadratic_path(anchors)\n            # Shift any handles which ended up on top of\n            # the previous anchor\n            a0 = new_subpath[0:-1:2]\n            h = new_subpath[1::2]\n            a1 = new_subpath[2::2]\n            false_ends = np.equal(a0, h).all(1)\n            h[false_ends] = 0.5 * (a0[false_ends] + a1[false_ends])\n            # Avoid degenerate handles (duplicate points) to prevent visual bug\n            degenerate_handles = np.equal(h, a1).all(1)\n            h[degenerate_handles] = 0.5 * (a0[degenerate_handles] + a1[degenerate_handles])\n            self.add_subpath(new_subpath)\n        return self\n\n    def make_smooth(self, approx=True, recurse=True) -> Self:\n        \"\"\"\n        Edits the path so as to pass smoothly through all\n        the current anchor points.\n\n        If approx is False, this may increase the total\n        number of points.\n        \"\"\"\n        mode = \"approx_smooth\" if approx else \"true_smooth\"\n        for submob in self.get_family(recurse):\n            if submob.is_smooth():\n                continue\n            submob.change_anchor_mode(mode)\n        return self\n\n    def make_approximately_smooth(self, recurse=True) -> Self:\n        self.make_smooth(approx=True, recurse=recurse)\n        return self\n\n    def make_jagged(self, recurse=True) -> Self:\n        for submob in self.get_family(recurse):\n            submob.change_anchor_mode(\"jagged\")\n        return self\n\n    def add_subpath(self, points: Vect3Array) -> Self:\n        assert len(points) % 2 == 1 or len(points) == 0\n        if not self.has_points():\n            self.set_points(points)\n            return self\n        if not self.consider_points_equal(points[0], self.get_points()[-1]):\n            self.start_new_path(points[0])\n        self.append_points(points[1:])\n        return self\n\n    def append_vectorized_mobject(self, vmobject: VMobject) -> Self:\n        self.add_subpath(vmobject.get_points())\n        n = vmobject.get_num_points()\n        self.data[-n:] = vmobject.data\n        return self\n\n    #\n    def consider_points_equal(self, p0: Vect3, p1: Vect3) -> bool:\n        return all(abs(p1 - p0) < self.tolerance_for_point_equality)\n\n    # Information about the curve\n    def get_bezier_tuples_from_points(self, points: Vect3Array) -> Iterable[Vect3Array]:\n        n_curves = (len(points) - 1) // 2\n        return (points[2 * i:2 * i + 3] for i in range(n_curves))\n\n    def get_bezier_tuples(self) -> Iterable[Vect3Array]:\n        return self.get_bezier_tuples_from_points(self.get_points())\n\n    def get_subpath_end_indices_from_points(self, points: Vect3Array) -> np.ndarray:\n        atol = 1e-4  # TODO, this is too unsystematic\n        a0, h, a1 = points[0:-1:2], points[1::2], points[2::2]\n        # An anchor point is considered the end of a path\n        # if its following handle is sitting on top of it.\n        # To disambiguate this from cases with many null\n        # curves in a row, we also check that the following\n        # anchor is genuinely distinct\n        is_end = (a0 == h).all(1) & (abs(h - a1) > atol).any(1)\n        end_indices = (2 * n for n, end in enumerate(is_end) if end)\n        return np.array([*end_indices, len(points) - 1])\n\n    def get_subpath_end_indices(self) -> np.ndarray:\n        if self.subpath_end_indices is None:\n            self.subpath_end_indices = self.get_subpath_end_indices_from_points(self.get_points())\n        return self.subpath_end_indices\n\n    def get_subpaths_from_points(self, points: Vect3Array) -> list[Vect3Array]:\n        if len(points) == 0:\n            return []\n        end_indices = self.get_subpath_end_indices_from_points(points)\n        start_indices = [0, *(end_indices[:-1] + 2)]\n        return [points[i1:i2 + 1] for i1, i2 in zip(start_indices, end_indices)]\n\n    def get_subpaths(self) -> list[Vect3Array]:\n        return self.get_subpaths_from_points(self.get_points())\n\n    def get_nth_curve_points(self, n: int) -> Vect3Array:\n        assert n < self.get_num_curves()\n        return self.get_points()[2 * n:2 * n + 3]\n\n    def get_nth_curve_function(self, n: int) -> Callable[[float], Vect3]:\n        return bezier(self.get_nth_curve_points(n))\n\n    def get_num_curves(self) -> int:\n        return self.get_num_points() // 2\n\n    def quick_point_from_proportion(self, alpha: float) -> Vect3:\n        # Assumes all curves have the same length, so is inaccurate\n        num_curves = self.get_num_curves()\n        if num_curves == 0:\n            return self.get_center()\n        n, residue = integer_interpolate(0, num_curves, alpha)\n        curve_func = self.get_nth_curve_function(n)\n        return curve_func(residue)\n\n    def curve_and_prop_of_partial_point(self, alpha) -> Tuple[int, float]:\n        \"\"\"\n        If you want a point a proportion alpha along the curve, this\n        gives you the index of the appropriate bezier curve, together\n        with the proportion along that curve you'd need to travel\n        \"\"\"\n        if alpha == 0:\n            return (0, 0.0)\n        partials: list[float] = [0]\n        for tup in self.get_bezier_tuples():\n            if self.consider_points_equal(tup[0], tup[1]):\n                # Don't consider null curves\n                arclen = 0\n            else:\n                # Approximate length with straight line from start to end\n                arclen = get_norm(tup[2] - tup[0])\n            partials.append(partials[-1] + arclen)\n        full = partials[-1]\n        if full == 0:\n            return len(partials), 1.0\n        # First index where the partial length is more than alpha times the full length\n        index = next(\n            (i for i, x in enumerate(partials) if x >= full * alpha),\n            len(partials) - 1  # Default\n        )\n        residue = float(inverse_interpolate(\n            partials[index - 1] / full, partials[index] / full, alpha\n        ))\n        return index - 1, residue\n\n    def point_from_proportion(self, alpha: float) -> Vect3:\n        if alpha <= 0:\n            return self.get_start()\n        elif alpha >= 1:\n            return self.get_end()\n        if self.get_num_points() == 0:\n            return self.get_center()\n        index, residue = self.curve_and_prop_of_partial_point(alpha)\n        return self.get_nth_curve_function(index)(residue)\n\n    def get_anchors_and_handles(self) -> list[Vect3]:\n        \"\"\"\n        returns anchors1, handles, anchors2,\n        where (anchors1[i], handles[i], anchors2[i])\n        will be three points defining a quadratic bezier curve\n        for any i in range(0, len(anchors1))\n        \"\"\"\n        points = self.get_points()\n        return [points[0:-1:2], points[1::2], points[2::2]]\n\n    def get_start_anchors(self) -> Vect3Array:\n        return self.get_points()[0:-1:2]\n\n    def get_end_anchors(self) -> Vect3:\n        return self.get_points()[2::2]\n\n    def get_anchors(self) -> Vect3Array:\n        return self.get_points()[::2]\n\n    def get_points_without_null_curves(self, atol: float = 1e-9) -> Vect3Array:\n        new_points = [self.get_points()[0]]\n        for tup in self.get_bezier_tuples():\n            if get_norm(tup[1] - tup[0]) > atol or get_norm(tup[2] - tup[0]) > atol:\n                new_points.append(tup[1:])\n        return np.vstack(new_points)\n\n    def get_arc_length(self, n_sample_points: int | None = None) -> float:\n        if n_sample_points is not None:\n            points = np.array([\n                self.quick_point_from_proportion(a)\n                for a in np.linspace(0, 1, n_sample_points)\n            ])\n            return poly_line_length(points)\n        points = self.get_points()\n        inner_len = poly_line_length(points[::2])\n        outer_len = poly_line_length(points)\n        return interpolate(inner_len, outer_len, 1 / 3)\n\n    def get_area_vector(self) -> Vect3:\n        # Returns a vector whose length is the area bound by\n        # the polygon formed by the anchor points, pointing\n        # in a direction perpendicular to the polygon according\n        # to the right hand rule.\n        if not self.has_points():\n            return np.zeros(3)\n\n        area = np.zeros(3)\n        for subpath in self.get_subpaths():\n            p0 = subpath[::2]  # anchors for this subpath\n            if len(p0) == 0:\n                continue\n            p1 = np.vstack([p0[1:], p0[0]])\n\n            # Each term goes through all edges [(x0, y0, z0), (x1, y1, z1)]\n            sums = p0 + p1\n            diffs = p1 - p0\n            area += 0.5 * np.array([\n                (sums[:, 1] * diffs[:, 2]).sum(),  # Add up (y0 + y1)*(z1 - z0)\n                (sums[:, 2] * diffs[:, 0]).sum(),  # Add up (z0 + z1)*(x1 - x0)\n                (sums[:, 0] * diffs[:, 1]).sum(),  # Add up (x0 + x1)*(y1 - y0)\n            ])\n        return area\n\n    def get_unit_normal(self, refresh: bool = False) -> Vect3:\n        if self.get_num_points() < 3:\n            return OUT\n\n        if not self.needs_new_unit_normal and not refresh:\n            return self.data[\"base_normal\"][1, :]\n\n        area_vect = self.get_area_vector()\n        area = get_norm(area_vect)\n        if area > 0:\n            normal = area_vect / area\n        else:\n            p = self.get_points()\n            normal = get_unit_normal(p[1] - p[0], p[2] - p[1])\n        self.data[\"base_normal\"][1::2] = normal\n        self.needs_new_unit_normal = False\n        return normal\n\n    def refresh_unit_normal(self) -> Self:\n        self.needs_new_unit_normal = True\n        return self\n\n    def rotate(\n        self,\n        angle: float,\n        axis: Vect3 = OUT,\n        about_point: Vect3 | None = None,\n        **kwargs\n    ) -> Self:\n        super().rotate(angle, axis, about_point, **kwargs)\n        for mob in self.get_family():\n            mob.refresh_unit_normal()\n        return self\n\n    def ensure_positive_orientation(self, recurse=True) -> Self:\n        for mob in self.get_family(recurse):\n            if mob.get_unit_normal()[2] < 0:\n                mob.reverse_points()\n        return self\n\n    # Alignment\n    def align_points(self, vmobject: VMobject) -> Self:\n        if self.get_num_points() == len(vmobject.get_points()):\n            for mob in [self, vmobject]:\n                mob.get_joint_angles()\n            return self\n\n        for mob in self, vmobject:\n            # If there are no points, add one to\n            # where the \"center\" is\n            if not mob.has_points():\n                mob.start_new_path(mob.get_center())\n\n        # Figure out what the subpaths are, and align\n        subpaths1 = self.get_subpaths()\n        subpaths2 = vmobject.get_subpaths()\n        for subpaths in [subpaths1, subpaths2]:\n            subpaths.sort(key=lambda sp: -sum(\n                get_norm(p2 - p1)\n                for p1, p2 in zip(sp, sp[1:])\n            ))\n        n_subpaths = max(len(subpaths1), len(subpaths2))\n\n        # Start building new ones\n        new_subpaths1 = []\n        new_subpaths2 = []\n\n        def get_nth_subpath(path_list, n):\n            if n >= len(path_list):\n                return np.vstack([path_list[0][:-1], path_list[0][::-1]])\n            return path_list[n]\n\n        for n in range(n_subpaths):\n            sp1 = get_nth_subpath(subpaths1, n)\n            sp2 = get_nth_subpath(subpaths2, n)\n            diff1 = max(0, (len(sp2) - len(sp1)) // 2)\n            diff2 = max(0, (len(sp1) - len(sp2)) // 2)\n            sp1 = self.insert_n_curves_to_point_list(diff1, sp1)\n            sp2 = self.insert_n_curves_to_point_list(diff2, sp2)\n            if n > 0:\n                # Add intermediate anchor to mark path end\n                new_subpaths1.append(new_subpaths1[-1][-1])\n                new_subpaths2.append(new_subpaths2[-1][-1])\n            new_subpaths1.append(sp1)\n            new_subpaths2.append(sp2)\n\n        for mob, paths in [(self, new_subpaths1), (vmobject, new_subpaths2)]:\n            new_points = np.vstack(paths)\n            mob.resize_points(len(new_points), resize_func=resize_preserving_order)\n            mob.set_points(new_points)\n            mob.get_joint_angles()\n        return self\n\n    def insert_n_curves(self, n: int, recurse: bool = True) -> Self:\n        for mob in self.get_family(recurse):\n            if mob.get_num_curves() > 0:\n                new_points = mob.insert_n_curves_to_point_list(n, mob.get_points())\n                mob.set_points(new_points)\n        return self\n\n    def insert_n_curves_to_point_list(self, n: int, points: Vect3Array) -> Vect3Array:\n        if len(points) == 1:\n            return np.repeat(points, 2 * n + 1, 0)\n\n        bezier_tuples = list(self.get_bezier_tuples_from_points(points))\n        atol = self.tolerance_for_point_equality\n        norms = [\n            0 if get_norm(tup[1] - tup[0]) < atol else get_norm(tup[2] - tup[0])\n            for tup in bezier_tuples\n        ]\n        # Calculate insertions per curve (ipc)\n        ipc = np.zeros(len(bezier_tuples), dtype=int)\n        for _ in range(n):\n            index = np.argmax(norms)\n            ipc[index] += 1\n            norms[index] *= ipc[index] / (ipc[index] + 1)\n\n        new_points = [points[0]]\n        for tup, n_inserts in zip(bezier_tuples, ipc):\n            # What was once a single quadratic curve defined\n            # by \"tup\" will now be broken into n_inserts + 1\n            # smaller quadratic curves\n            alphas = np.linspace(0, 1, n_inserts + 2)\n            for a1, a2 in zip(alphas, alphas[1:]):\n                new_points.extend(partial_quadratic_bezier_points(tup, a1, a2)[1:])\n        return np.vstack(new_points)\n\n    def pointwise_become_partial(self, vmobject: VMobject, a: float, b: float) -> Self:\n        assert isinstance(vmobject, VMobject)\n        vm_points = vmobject.get_points()\n        self.data[\"joint_angle\"] = vmobject.data[\"joint_angle\"]\n        if a <= 0 and b >= 1:\n            self.set_points(vm_points, refresh=False)\n            return self\n        num_curves = vmobject.get_num_curves()\n\n        # Partial curve includes three portions:\n        # - A start, which is some ending portion of an inner quadratic\n        # - A middle section, which matches the curve exactly\n        # - An end, which is the starting portion of a later inner quadratic\n\n        lower_index, lower_residue = integer_interpolate(0, num_curves, a)\n        upper_index, upper_residue = integer_interpolate(0, num_curves, b)\n        i1 = 2 * lower_index\n        i2 = 2 * lower_index + 3\n        i3 = 2 * upper_index\n        i4 = 2 * upper_index + 3\n\n        new_points = vm_points.copy()\n        if num_curves == 0:\n            new_points[:] = 0\n            return self\n        if lower_index == upper_index:\n            tup = partial_quadratic_bezier_points(vm_points[i1:i2], lower_residue, upper_residue)\n            new_points[:i1] = tup[0]\n            new_points[i1:i4] = tup\n            new_points[i4:] = tup[2]\n        else:\n            low_tup = partial_quadratic_bezier_points(vm_points[i1:i2], lower_residue, 1)\n            high_tup = partial_quadratic_bezier_points(vm_points[i3:i4], 0, upper_residue)\n            new_points[0:i1] = low_tup[0]\n            new_points[i1:i2] = low_tup\n            # Keep new_points i2:i3 as they are\n            new_points[i3:i4] = high_tup\n            new_points[i4:] = high_tup[2]\n        self.data[\"joint_angle\"][:i1] = 0\n        self.data[\"joint_angle\"][i4:] = 0\n        self.set_points(new_points, refresh=False)\n        return self\n\n    def get_subcurve(self, a: float, b: float) -> Self:\n        vmob = self.copy()\n        vmob.pointwise_become_partial(self, a, b)\n        return vmob\n\n    def get_outer_vert_indices(self) -> np.ndarray:\n        \"\"\"\n        Returns the pattern (0, 1, 2, 2, 3, 4, 4, 5, 6, ...)\n        \"\"\"\n        n_curves = self.get_num_curves()\n        if len(self.outer_vert_indices) != 3 * n_curves:\n            # Creates the pattern (0, 1, 2, 2, 3, 4, 4, 5, 6, ...)\n            self.outer_vert_indices = (np.arange(1, 3 * n_curves + 1) * 2) // 3\n        return self.outer_vert_indices\n\n    # Data for shaders that may need refreshing\n\n    def get_triangulation(self) -> np.ndarray:\n        # Figure out how to triangulate the interior to know\n        # how to send the points as to the vertex shader.\n        # First triangles come directly from the points\n        points = self.get_points()\n\n        if len(points) <= 1:\n            return np.zeros(0, dtype='i4')\n\n        normal_vector = self.get_unit_normal()\n\n        # Rotate points such that unit normal vector is OUT\n        if not np.isclose(normal_vector, OUT).all():\n            points = np.dot(points, z_to_vector(normal_vector))\n\n        v01s = points[1::2] - points[0:-1:2]\n        v12s = points[2::2] - points[1::2]\n        curve_orientations = np.sign(cross2d(v01s, v12s))\n\n        concave_parts = curve_orientations < 0\n\n        # These are the vertices to which we'll apply a polygon triangulation\n        indices = np.arange(len(points), dtype=int)\n        inner_vert_indices = np.hstack([\n            indices[0::2],\n            indices[1::2][concave_parts],\n        ])\n        inner_vert_indices.sort()\n        # Even indices correspond to anchors, and `end_indices // 2`\n        # shows which anchors are considered end points\n        end_indices = self.get_subpath_end_indices()\n        counts = np.arange(1, len(inner_vert_indices) + 1)\n        rings = counts[inner_vert_indices % 2 == 0][end_indices // 2]\n\n        # Triangulate\n        inner_verts = points[inner_vert_indices]\n        inner_tri_indices = inner_vert_indices[\n            earclip_triangulation(inner_verts, rings)\n        ]\n        # Remove null triangles, coming from adjascent points\n        iti = inner_tri_indices\n        null1 = (iti[0::3] + 1 == iti[1::3]) & (iti[0::3] + 2 == iti[2::3])\n        null2 = (iti[0::3] - 1 == iti[1::3]) & (iti[0::3] - 2 == iti[2::3])\n        inner_tri_indices = iti[~(null1 | null2).repeat(3)]\n\n        ovi = self.get_outer_vert_indices()\n        tri_indices = np.hstack([ovi, inner_tri_indices])\n        return tri_indices\n\n    def refresh_joint_angles(self) -> Self:\n        for mob in self.get_family():\n            mob.needs_new_joint_angles = True\n        return self\n\n    def get_joint_angles(self, refresh: bool = False) -> np.ndarray:\n        \"\"\"\n        The 'joint product' is a 4-vector holding the cross and dot\n        product between tangent vectors at a joint\n        \"\"\"\n        if not self.needs_new_joint_angles and not refresh:\n            return self.data[\"joint_angle\"][:, 0]\n\n        if \"joint_angle\" in self.locked_data_keys:\n            return self.data[\"joint_angle\"][:, 0]\n\n        self.needs_new_joint_angles = False\n        self._data_has_changed = True\n\n        # Rotate points such that positive z direction is the normal\n        points = self.get_points() @ rotation_between_vectors(OUT, self.get_unit_normal())\n\n        if len(points) < 3:\n            return self.data[\"joint_angle\"][:, 0]\n\n        # Find all the unit tangent vectors at each joint\n        a0, h, a1 = points[0:-1:2], points[1::2], points[2::2]\n        a0_to_h = h - a0\n        h_to_a1 = a1 - h\n\n        # Tangent vectors into each vertex\n        v_in = np.zeros(points.shape)\n        # Tangent vectors out of each vertex\n        v_out = np.zeros(points.shape)\n\n        v_in[1::2] = a0_to_h\n        v_in[2::2] = h_to_a1\n        v_out[0:-1:2] = a0_to_h\n        v_out[1::2] = h_to_a1\n\n        # Joint up closed loops, or mark unclosed paths as such\n        ends = self.get_subpath_end_indices()\n        starts = [0, *(e + 2 for e in ends[:-1])]\n        for start, end in zip(starts, ends):\n            if start == end:\n                continue\n            if (points[start] == points[end]).all():\n                v_in[start] = v_out[end - 1]\n                v_out[end] = v_in[start + 1]\n            else:\n                v_in[start] = v_out[start]\n                v_out[end] = v_in[end]\n\n        # Find the angles between vectors into each vertex, and out of it\n        angles_in = np.arctan2(v_in[:, 1], v_in[:, 0])\n        angles_out = np.arctan2(v_out[:, 1], v_out[:, 0])\n        angle_diffs = angles_out - angles_in\n        angle_diffs[angle_diffs < -PI] += TAU\n        angle_diffs[angle_diffs > PI] -= TAU\n        self.data[\"joint_angle\"][:, 0] = angle_diffs\n        return self.data[\"joint_angle\"][:, 0]\n\n    def lock_matching_data(self, vmobject1: VMobject, vmobject2: VMobject) -> Self:\n        for mob in [self, vmobject1, vmobject2]:\n            mob.get_joint_angles()\n        super().lock_matching_data(vmobject1, vmobject2)\n        return self\n\n    def triggers_refresh(func: Callable):\n        @wraps(func)\n        def wrapper(self, *args, refresh=True, **kwargs):\n            func(self, *args, **kwargs)\n            if refresh:\n                self.subpath_end_indices = None\n                self.refresh_joint_angles()\n                self.refresh_unit_normal()\n            return self\n        return wrapper\n\n    @triggers_refresh\n    def set_points(self, points: Vect3Array) -> Self:\n        assert len(points) == 0 or len(points) % 2 == 1\n        return super().set_points(points)\n\n    @triggers_refresh\n    def append_points(self, points: Vect3Array) -> Self:\n        assert len(points) % 2 == 0\n        return super().append_points(points)\n\n    def reverse_points(self, recurse: bool = True) -> Self:\n        # This will reset which anchors are\n        # considered path ends\n        for mob in self.get_family(recurse):\n            if not mob.has_points():\n                continue\n            inner_ends = mob.get_subpath_end_indices()[:-1]\n            mob.data[\"point\"][inner_ends + 1] = mob.data[\"point\"][inner_ends + 2]\n            mob.data[\"base_normal\"][1::2] *= -1  # Invert normal vector\n            self.subpath_end_indices = None\n        return super().reverse_points()\n\n    @triggers_refresh\n    def set_data(self, data: np.ndarray) -> Self:\n        return super().set_data(data)\n\n    # TODO, how to be smart about tangents here?\n    @triggers_refresh\n    def apply_function(\n        self,\n        function: Callable[[Vect3], Vect3],\n        make_smooth: bool = False,\n        **kwargs\n    ) -> Self:\n        super().apply_function(function, **kwargs)\n        if self.make_smooth_after_applying_functions or make_smooth:\n            self.make_smooth(approx=True)\n        return self\n\n    @triggers_refresh\n    def stretch(self, *args, **kwargs) -> Self:\n        return super().stretch(*args, **kwargs)\n\n    @triggers_refresh\n    def apply_matrix(self, *args, **kwargs) -> Self:\n        return super().apply_matrix(*args, **kwargs)\n\n    def rotate(\n        self,\n        angle: float,\n        axis: Vect3 = OUT,\n        about_point: Vect3 | None = None,\n        **kwargs\n    ) -> Self:\n        rot_matrix_T = rotation_matrix_transpose(angle, axis)\n        self.apply_points_function(\n            lambda points: np.dot(points, rot_matrix_T),\n            about_point,\n            **kwargs\n        )\n        for mob in self.get_family():\n            mob.get_unit_normal(refresh=True)\n        return self\n\n    def set_animating_status(self, is_animating: bool, recurse: bool = True):\n        super().set_animating_status(is_animating, recurse)\n        for submob in self.get_family(recurse):\n            submob.get_joint_angles(refresh=True)\n        return self\n\n    # For shaders\n\n    def init_shader_wrapper(self, ctx: Context):\n        self.shader_wrapper = VShaderWrapper(\n            ctx=ctx,\n            vert_data=self.data,\n            mobject_uniforms=self.uniforms,\n            code_replacements=self.shader_code_replacements,\n            stroke_behind=self.stroke_behind,\n            depth_test=self.depth_test\n        )\n\n    def refresh_shader_wrapper_id(self):\n        for submob in self.get_family():\n            if submob.shader_wrapper is not None:\n                submob.shader_wrapper.stroke_behind = submob.stroke_behind\n        super().refresh_shader_wrapper_id()\n        return self\n\n    def get_shader_data(self) -> np.ndarray:\n        # Do we want this elsewhere? Say whenever points are refreshed or something?\n        self.get_joint_angles()\n        self.data[\"base_normal\"][0::2] = self.data[\"point\"][0]\n        return super().get_shader_data()\n\n    def get_shader_vert_indices(self) -> Optional[np.ndarray]:\n        return self.get_outer_vert_indices()\n\n\nclass VGroup(Group, VMobject, Generic[SubVmobjectType]):\n    def __init__(self, *vmobjects: SubVmobjectType | Iterable[SubVmobjectType], **kwargs):\n        super().__init__(**kwargs)\n        if any(isinstance(vmob, Mobject) and not isinstance(vmob, VMobject) for vmob in vmobjects):\n            raise Exception(\"Only VMobjects can be passed into VGroup\")\n        self._ingest_args(*vmobjects)\n        if self.submobjects:\n            self.uniforms.update(self.submobjects[0].uniforms)\n\n    def __add__(self, other: VMobject) -> Self:\n        assert isinstance(other, VMobject)\n        return self.add(other)\n\n    # This is just here to make linters happy with references to things like VGroup(...)[0]\n    def __getitem__(self, index) -> SubVmobjectType:\n        return super().__getitem__(index)\n\n\nclass VectorizedPoint(Point, VMobject):\n    def __init__(\n        self,\n        location: np.ndarray = ORIGIN,\n        color: ManimColor = BLACK,\n        fill_opacity: float = 0.0,\n        stroke_width: float = 0.0,\n        **kwargs\n    ):\n        Point.__init__(self, location, **kwargs)\n        VMobject.__init__(\n            self,\n            color=color,\n            fill_opacity=fill_opacity,\n            stroke_width=stroke_width,\n            **kwargs\n        )\n        self.set_points(np.array([location]))\n\n\nclass CurvesAsSubmobjects(VGroup):\n    def __init__(self, vmobject: VMobject, **kwargs):\n        super().__init__(**kwargs)\n        for tup in vmobject.get_bezier_tuples():\n            part = VMobject()\n            part.set_points(tup)\n            part.match_style(vmobject)\n            self.add(part)\n\n\nclass DashedVMobject(VMobject):\n    def __init__(\n        self,\n        vmobject: VMobject,\n        num_dashes: int = 15,\n        positive_space_ratio: float = 0.5,\n        **kwargs\n    ):\n        super().__init__(**kwargs)\n\n        if num_dashes > 0:\n            # End points of the unit interval for division\n            alphas = np.linspace(0, 1, num_dashes + 1)\n\n            # This determines the length of each \"dash\"\n            full_d_alpha = (1.0 / num_dashes)\n            partial_d_alpha = full_d_alpha * positive_space_ratio\n\n            # Rescale so that the last point of vmobject will\n            # be the end of the last dash\n            alphas /= (1 - full_d_alpha + partial_d_alpha)\n\n            self.add(*[\n                vmobject.get_subcurve(alpha, alpha + partial_d_alpha)\n                for alpha in alphas[:-1]\n            ])\n        # Family is already taken care of by get_subcurve\n        # implementation\n        self.match_style(vmobject, recurse=False)\n\n\nclass VHighlight(VGroup):\n    def __init__(\n        self,\n        vmobject: VMobject,\n        n_layers: int = 5,\n        color_bounds: Tuple[ManimColor] = (GREY_C, GREY_E),\n        max_stroke_addition: float = 5.0,\n    ):\n        outline = vmobject.replicate(n_layers)\n        outline.set_fill(opacity=0)\n        added_widths = np.linspace(0, max_stroke_addition, n_layers + 1)[1:]\n        colors = color_gradient(color_bounds, n_layers)\n        for part, added_width, color in zip(reversed(outline), added_widths, colors):\n            for sm in part.family_members_with_points():\n                sm.set_stroke(\n                    width=sm.get_stroke_width() + added_width,\n                    color=color,\n                )\n        super().__init__(*outline)\n"
  },
  {
    "path": "manimlib/mobject/value_tracker.py",
    "content": "from __future__ import annotations\n\nimport numpy as np\nfrom manimlib.mobject.mobject import Mobject\nfrom manimlib.utils.iterables import listify\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from manimlib.typing import Self\n\n\nclass ValueTracker(Mobject):\n    \"\"\"\n    Not meant to be displayed.  Instead the position encodes some\n    number, often one which another animation or continual_animation\n    uses for its update function, and by treating it as a mobject it can\n    still be animated and manipulated just like anything else.\n    \"\"\"\n    value_type: type = np.float64\n\n    def __init__(\n        self,\n        value: float | complex | np.ndarray = 0,\n        **kwargs\n    ):\n        self.value = value\n        super().__init__(**kwargs)\n\n    def init_uniforms(self) -> None:\n        super().init_uniforms()\n        self.uniforms[\"value\"] = np.array(\n            listify(self.value),\n            dtype=self.value_type,\n        )\n\n    def get_value(self) -> float | complex | np.ndarray:\n        result = self.uniforms[\"value\"]\n        if len(result) == 1:\n            return result[0]\n        return result\n\n    def set_value(self, value: float | complex | np.ndarray) -> Self:\n        self.uniforms[\"value\"][:] = value\n        return self\n\n    def increment_value(self, d_value: float | complex) -> None:\n        self.set_value(self.get_value() + d_value)\n\n\nclass ExponentialValueTracker(ValueTracker):\n    \"\"\"\n    Operates just like ValueTracker, except it encodes the value as the\n    exponential of a position coordinate, which changes how interpolation\n    behaves\n    \"\"\"\n\n    def get_value(self) -> float | complex:\n        return np.exp(ValueTracker.get_value(self))\n\n    def set_value(self, value: float | complex):\n        return ValueTracker.set_value(self, np.log(value))\n\n\nclass ComplexValueTracker(ValueTracker):\n    value_type: type = np.complex128\n"
  },
  {
    "path": "manimlib/mobject/vector_field.py",
    "content": "from __future__ import annotations\n\nimport itertools as it\n\nimport numpy as np\nfrom scipy.integrate import solve_ivp\n\nfrom manimlib.constants import FRAME_HEIGHT, FRAME_WIDTH\nfrom manimlib.constants import DEFAULT_MOBJECT_COLOR\nfrom manimlib.animation.indication import VShowPassingFlash\nfrom manimlib.mobject.types.vectorized_mobject import VGroup\nfrom manimlib.mobject.types.vectorized_mobject import VMobject\nfrom manimlib.utils.bezier import interpolate\nfrom manimlib.utils.bezier import inverse_interpolate\nfrom manimlib.utils.color import get_colormap_list\nfrom manimlib.utils.color import get_color_map\nfrom manimlib.utils.iterables import cartesian_product\nfrom manimlib.utils.rate_functions import linear\nfrom manimlib.utils.space_ops import get_norm\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable, Iterable, Sequence, TypeVar, Tuple, Optional\n    from manimlib.typing import ManimColor, Vect3, VectN, VectArray, Vect3Array, Vect4Array\n\n    from manimlib.mobject.coordinate_systems import CoordinateSystem\n    from manimlib.mobject.mobject import Mobject\n\n    T = TypeVar(\"T\")\n\n\n#### Delete these two ###\ndef get_vectorized_rgb_gradient_function(\n    min_value: T,\n    max_value: T,\n    color_map: str\n) -> Callable[[VectN], Vect3Array]:\n    rgbs = np.array(get_colormap_list(color_map))\n\n    def func(values):\n        alphas = inverse_interpolate(\n            min_value, max_value, np.array(values)\n        )\n        alphas = np.clip(alphas, 0, 1)\n        scaled_alphas = alphas * (len(rgbs) - 1)\n        indices = scaled_alphas.astype(int)\n        next_indices = np.clip(indices + 1, 0, len(rgbs) - 1)\n        inter_alphas = scaled_alphas % 1\n        inter_alphas = inter_alphas.repeat(3).reshape((len(indices), 3))\n        result = interpolate(rgbs[indices], rgbs[next_indices], inter_alphas)\n        return result\n\n    return func\n\n\ndef get_rgb_gradient_function(\n    min_value: T,\n    max_value: T,\n    color_map: str\n) -> Callable[[float], Vect3]:\n    vectorized_func = get_vectorized_rgb_gradient_function(min_value, max_value, color_map)\n    return lambda value: vectorized_func(np.array([value]))[0]\n####\n\n\ndef ode_solution_points(function, state0, time, dt=0.01):\n    solution = solve_ivp(\n        lambda t, state: function(state),\n        t_span=(0, time),\n        y0=state0,\n        t_eval=np.arange(0, time, dt)\n    )\n    return solution.y.T\n\n\ndef move_along_vector_field(\n    mobject: Mobject,\n    func: Callable[[Vect3], Vect3]\n) -> Mobject:\n    mobject.add_updater(\n        lambda m, dt: m.shift(\n            func(m.get_center()) * dt\n        )\n    )\n    return mobject\n\n\ndef move_submobjects_along_vector_field(\n    mobject: Mobject,\n    func: Callable[[Vect3], Vect3]\n) -> Mobject:\n    def apply_nudge(mob, dt):\n        for submob in mob:\n            x, y = submob.get_center()[:2]\n            if abs(x) < FRAME_WIDTH and abs(y) < FRAME_HEIGHT:\n                submob.shift(func(submob.get_center()) * dt)\n\n    mobject.add_updater(apply_nudge)\n    return mobject\n\n\ndef move_points_along_vector_field(\n    mobject: Mobject,\n    func: Callable[[float, float], Iterable[float]],\n    coordinate_system: CoordinateSystem\n) -> Mobject:\n    cs = coordinate_system\n    origin = cs.get_origin()\n\n    def apply_nudge(mob, dt):\n        mob.apply_function(\n            lambda p: p + (cs.c2p(*func(*cs.p2c(p))) - origin) * dt\n        )\n    mobject.add_updater(apply_nudge)\n    return mobject\n\n\ndef get_sample_coords(\n    coordinate_system: CoordinateSystem,\n    density: float = 1.0\n) -> it.product[tuple[Vect3, ...]]:\n    ranges = []\n    for range_args in coordinate_system.get_all_ranges():\n        _min, _max, step = range_args\n        step /= density\n        ranges.append(np.arange(_min, _max + step, step))\n    return np.array(list(it.product(*ranges)))\n\n\ndef vectorize(pointwise_function: Callable[[Tuple], Tuple]):\n    def v_func(coords_array: VectArray) -> VectArray:\n        return np.array([pointwise_function(*coords) for coords in coords_array])\n\n    return v_func\n\n\n# Mobjects\n\n\nclass VectorField(VMobject):\n    def __init__(\n        self,\n        # Vectorized function: Takes in an array of coordinates, returns an array of outputs.\n        func: Callable[[VectArray], VectArray],\n        # Typically a set of Axes or NumberPlane\n        coordinate_system: CoordinateSystem,\n        sample_coords: Optional[VectArray] = None,\n        density: float = 2.0,\n        magnitude_range: Optional[Tuple[float, float]] = None,\n        color: Optional[ManimColor] = None,\n        color_map_name: Optional[str] = \"3b1b_colormap\",\n        color_map: Optional[Callable[[Sequence[float]], Vect4Array]] = None,\n        stroke_opacity: float = 1.0,\n        stroke_width: float = 3,\n        tip_width_ratio: float = 4,\n        tip_len_to_width: float = 0.01,\n        max_vect_len: float | None = None,\n        max_vect_len_to_step_size: float = 0.8,\n        flat_stroke: bool = False,\n        norm_to_opacity_func=None,  # TODO, check on this\n        **kwargs\n    ):\n        self.func = func\n        self.coordinate_system = coordinate_system\n        self.stroke_width = stroke_width\n        self.tip_width_ratio = tip_width_ratio\n        self.tip_len_to_width = tip_len_to_width\n        self.norm_to_opacity_func = norm_to_opacity_func\n\n        # Search for sample_points\n        if sample_coords is not None:\n            self.sample_coords = sample_coords\n        else:\n            self.sample_coords = get_sample_coords(coordinate_system, density)\n        self.update_sample_points()\n\n        if max_vect_len is None:\n            step_size = get_norm(self.sample_points[1] - self.sample_points[0])\n            self.max_displayed_vect_len = max_vect_len_to_step_size * step_size\n        else:\n            self.max_displayed_vect_len = max_vect_len * coordinate_system.x_axis.get_unit_size()\n\n        # Prepare the color map\n        if magnitude_range is None:\n            max_value = max(map(get_norm, func(self.sample_coords)))\n            magnitude_range = (0, max_value)\n\n        self.magnitude_range = magnitude_range\n\n        if color is not None:\n            self.color_map = None\n        else:\n            self.color_map = color_map or get_color_map(color_map_name)\n\n        self.init_base_stroke_width_array(len(self.sample_coords))\n\n        super().__init__(\n            stroke_opacity=stroke_opacity,\n            flat_stroke=flat_stroke,\n            **kwargs\n        )\n        self.set_stroke(color, stroke_width)\n        self.update_vectors()\n\n    def init_points(self):\n        n_samples = len(self.sample_coords)\n        self.set_points(np.zeros((8 * n_samples - 1, 3)))\n        self.set_joint_type('no_joint')\n\n    def get_sample_points(\n        self,\n        center: np.ndarray,\n        width: float,\n        height: float,\n        depth: float,\n        x_density: float,\n        y_density: float,\n        z_density: float\n    ) -> np.ndarray:\n        to_corner = np.array([width / 2, height / 2, depth / 2])\n        spacings = 1.0 / np.array([x_density, y_density, z_density])\n        to_corner = spacings * (to_corner / spacings).astype(int)\n        lower_corner = center - to_corner\n        upper_corner = center + to_corner + spacings\n        return cartesian_product(*(\n            np.arange(low, high, space)\n            for low, high, space in zip(lower_corner, upper_corner, spacings)\n        ))\n\n    def init_base_stroke_width_array(self, n_sample_points):\n        arr = np.ones(8 * n_sample_points - 1)\n        arr[4::8] = self.tip_width_ratio\n        arr[5::8] = self.tip_width_ratio * 0.5\n        arr[6::8] = 0\n        arr[7::8] = 0\n        self.base_stroke_width_array = arr\n\n    def set_sample_coords(self, sample_coords: VectArray):\n        self.sample_coords = sample_coords\n        return self\n\n    def set_stroke(self, color=None, width=None, opacity=None, behind=None, flat=None, recurse=True):\n        super().set_stroke(color, None, opacity, behind, flat, recurse)\n        if width is not None:\n            self.set_stroke_width(float(width))\n        return self\n\n    def set_stroke_width(self, width: float):\n        if self.get_num_points() > 0:\n            self.get_stroke_widths()[:] = width * self.base_stroke_width_array\n            self.stroke_width = width\n        return self\n\n    def update_sample_points(self):\n        self.sample_points = self.coordinate_system.c2p(*self.sample_coords.T)\n\n    def update_vectors(self):\n        tip_width = self.tip_width_ratio * self.stroke_width\n        tip_len = self.tip_len_to_width * tip_width\n\n        # Outputs in the coordinate system\n        outputs = self.func(self.sample_coords)\n        output_norms = np.linalg.norm(outputs, axis=1)[:, np.newaxis]\n\n        # Corresponding vector values in global coordinates\n        out_vects = self.coordinate_system.c2p(*outputs.T) - self.coordinate_system.get_origin()\n        out_vect_norms = np.linalg.norm(out_vects, axis=1)[:, np.newaxis]\n        unit_outputs = np.zeros_like(out_vects)\n        np.true_divide(out_vects, out_vect_norms, out=unit_outputs, where=(out_vect_norms > 0))\n\n        # How long should the arrows be drawn, in global coordinates\n        max_len = self.max_displayed_vect_len\n        if max_len < np.inf:\n            drawn_norms = max_len * np.tanh(out_vect_norms / max_len)\n        else:\n            drawn_norms = out_vect_norms\n\n        # What's the distance from the base of an arrow to\n        # the base of its head?\n        dist_to_head_base = np.clip(drawn_norms - tip_len, 0, np.inf)  # Mixing units!\n\n        # Set all points\n        points = self.get_points()\n        points[0::8] = self.sample_points\n        points[2::8] = self.sample_points + dist_to_head_base * unit_outputs\n        points[4::8] = points[2::8]\n        points[6::8] = self.sample_points + drawn_norms * unit_outputs\n        for i in (1, 3, 5):\n            points[i::8] = 0.5 * (points[i - 1::8] + points[i + 1::8])\n        points[7::8] = points[6:-1:8]\n\n        # Adjust stroke widths\n        width_arr = self.stroke_width * self.base_stroke_width_array\n        width_scalars = np.clip(drawn_norms / tip_len, 0, 1)\n        width_scalars = np.repeat(width_scalars, 8)[:-1]\n        self.get_stroke_widths()[:] = width_scalars * width_arr\n\n        # Potentially adjust opacity and color\n        if self.color_map is not None:\n            self.get_stroke_colors()  # Ensures the array is updated to appropriate length\n            low, high = self.magnitude_range\n            self.data['stroke_rgba'][:, :3] = self.color_map(\n                inverse_interpolate(low, high, np.repeat(output_norms, 8)[:-1])\n            )[:, :3]\n\n        if self.norm_to_opacity_func is not None:\n            self.get_stroke_opacities()[:] = self.norm_to_opacity_func(\n                np.repeat(output_norms, 8)[:-1]\n            )\n\n        self.note_changed_data()\n        return self\n\n\nclass TimeVaryingVectorField(VectorField):\n    def __init__(\n        self,\n        # Takes in an array of points and a float for time\n        time_func: Callable[[VectArray, float], VectArray],\n        coordinate_system: CoordinateSystem,\n        **kwargs\n    ):\n        self.time = 0\n\n        def func(coords):\n            return time_func(coords, self.time)\n\n        super().__init__(func, coordinate_system, **kwargs)\n        self.add_updater(lambda m, dt: m.increment_time(dt))\n        self.always.update_vectors()\n\n    def increment_time(self, dt):\n        self.time += dt\n\n\nclass StreamLines(VGroup):\n    def __init__(\n        self,\n        func: Callable[[VectArray], VectArray],\n        coordinate_system: CoordinateSystem,\n        density: float = 1.0,\n        n_repeats: int = 1,\n        noise_factor: float | None = None,\n        # Config for drawing lines\n        solution_time: float = 3,\n        dt: float = 0.05,\n        arc_len: float = 3,\n        max_time_steps: int = 200,\n        n_samples_per_line: int = 10,\n        cutoff_norm: float = 15,\n        # Style info\n        stroke_width: float = 1.0,\n        stroke_color: ManimColor = DEFAULT_MOBJECT_COLOR,\n        stroke_opacity: float = 1,\n        color_by_magnitude: bool = True,\n        magnitude_range: Tuple[float, float] = (0, 2.0),\n        taper_stroke_width: bool = False,\n        color_map: str = \"3b1b_colormap\",\n        **kwargs\n    ):\n        super().__init__(**kwargs)\n        self.func = func\n        self.coordinate_system = coordinate_system\n        self.density = density\n        self.n_repeats = n_repeats\n        self.noise_factor = noise_factor\n        self.solution_time = solution_time\n        self.dt = dt\n        self.arc_len = arc_len\n        self.max_time_steps = max_time_steps\n        self.n_samples_per_line = n_samples_per_line\n        self.cutoff_norm = cutoff_norm\n        self.stroke_width = stroke_width\n        self.stroke_color = stroke_color\n        self.stroke_opacity = stroke_opacity\n        self.color_by_magnitude = color_by_magnitude\n        self.magnitude_range = magnitude_range\n        self.taper_stroke_width = taper_stroke_width\n        self.color_map = color_map\n\n        self.draw_lines()\n        self.init_style()\n\n    def point_func(self, points: Vect3Array) -> Vect3:\n        in_coords = np.array(self.coordinate_system.p2c(points)).T\n        out_coords = self.func(in_coords)\n        origin = self.coordinate_system.get_origin()\n        return self.coordinate_system.c2p(*out_coords.T) - origin\n\n    def draw_lines(self) -> None:\n        lines = []\n\n        # Todo, it feels like coordinate system should just have\n        # the ODE solver built into it, no?\n        lines = []\n        for coords in self.get_sample_coords():\n            solution_coords = ode_solution_points(self.func, coords, self.solution_time, self.dt)\n            line = VMobject()\n            line.set_points_smoothly(self.coordinate_system.c2p(*solution_coords.T))\n            # TODO, account for arc length somehow?\n            line.virtual_time = self.solution_time\n            lines.append(line)\n        self.set_submobjects(lines)\n\n    def get_sample_coords(self):\n        cs = self.coordinate_system\n        sample_coords = get_sample_coords(cs, self.density)\n\n        noise_factor = self.noise_factor\n        if noise_factor is None:\n            noise_factor = (cs.x_axis.get_unit_size() / self.density) * 0.5\n\n        return np.array([\n            coords + noise_factor * np.random.random(coords.shape)\n            for n in range(self.n_repeats)\n            for coords in sample_coords\n        ])\n\n    def init_style(self) -> None:\n        if self.color_by_magnitude:\n            values_to_rgbs = get_vectorized_rgb_gradient_function(\n                *self.magnitude_range, self.color_map,\n            )\n            cs = self.coordinate_system\n            for line in self.submobjects:\n                norms = [\n                    get_norm(self.func(cs.p2c(point)))\n                    for point in line.get_points()\n                ]\n                rgbs = values_to_rgbs(norms)\n                rgbas = np.zeros((len(rgbs), 4))\n                rgbas[:, :3] = rgbs\n                rgbas[:, 3] = self.stroke_opacity\n                line.set_rgba_array(rgbas, \"stroke_rgba\")\n        else:\n            self.set_stroke(self.stroke_color, opacity=self.stroke_opacity)\n\n        if self.taper_stroke_width:\n            width = [0, self.stroke_width, 0]\n        else:\n            width = self.stroke_width\n        self.set_stroke(width=width)\n\n\nclass AnimatedStreamLines(VGroup):\n    def __init__(\n        self,\n        stream_lines: StreamLines,\n        lag_range: float = 4,\n        rate_multiple: float = 1.0,\n        line_anim_config: dict = dict(\n            rate_func=linear,\n            time_width=1.0,\n        ),\n        **kwargs\n    ):\n        super().__init__(**kwargs)\n        self.stream_lines = stream_lines\n\n        for line in stream_lines:\n            line.anim = VShowPassingFlash(\n                line,\n                run_time=line.virtual_time / rate_multiple,\n                **line_anim_config,\n            )\n            line.anim.begin()\n            line.time = -lag_range * np.random.random()\n            self.add(line.anim.mobject)\n\n        self.add_updater(lambda m, dt: m.update(dt))\n\n    def update(self, dt: float = 0) -> None:\n        stream_lines = self.stream_lines\n        for line in stream_lines:\n            line.time += dt\n            adjusted_time = max(line.time, 0) % line.anim.run_time\n            line.anim.update(adjusted_time / line.anim.run_time)\n"
  },
  {
    "path": "manimlib/module_loader.py",
    "content": "from __future__ import annotations\n\nimport builtins\nimport importlib\nimport os\nimport sys\nimport sysconfig\n\nfrom manimlib.config import manim_config\nfrom manimlib.logger import log\n\nModule = importlib.util.types.ModuleType\n\n\nclass ModuleLoader:\n    \"\"\"\n    Utility class to load a module from a file and handle its imports.\n\n    Most parts of this class are only needed for the reload functionality,\n    while the `get_module` method is the main entry point to import a module.\n    \"\"\"\n\n    @staticmethod\n    def get_module(file_name: str | None, is_during_reload=False) -> Module | None:\n        \"\"\"\n        Imports a module from a file and returns it.\n\n        During reload (when the user calls `reload()` in the IPython shell), we\n        also track the imported modules and reload them as well (they would be\n        cached otherwise). See the reload_manager where the reload parameter is set.\n\n        Note that `exec_module()` is called twice when reloading a module:\n        1. In exec_module_and_track_imports to track the imports\n        2. Here to actually execute the module again with the respective\n           imported modules reloaded.\n        \"\"\"\n        if file_name is None:\n            return None\n\n        module_name = file_name.replace(os.sep, \".\").replace(\".py\", \"\")\n        spec = importlib.util.spec_from_file_location(module_name, file_name)\n        module = importlib.util.module_from_spec(spec)\n\n        if is_during_reload:\n            imported_modules = ModuleLoader._exec_module_and_track_imports(spec, module)\n            reloaded_modules_tracker = set()\n            ModuleLoader._reload_modules(imported_modules, reloaded_modules_tracker)\n\n        spec.loader.exec_module(module)\n        return module\n\n    @staticmethod\n    def _exec_module_and_track_imports(spec, module: Module) -> set[str]:\n        \"\"\"\n        Executes the given module (imports it) and returns all the modules that\n        are imported during its execution.\n\n        This is achieved by replacing the __import__ function with a custom one\n        that tracks the imported modules. At the end, the original __import__\n        built-in function is restored.\n        \"\"\"\n        imported_modules: set[str] = set()\n        original_import = builtins.__import__\n\n        def tracked_import(name, globals=None, locals=None, fromlist=(), level=0):\n            \"\"\"\n            Custom __import__ function that does exactly the same as the original\n            one, but also tracks the imported modules by means of adding their\n            names to a set.\n            \"\"\"\n            result = original_import(name, globals, locals, fromlist, level)\n            imported_modules.add(name)\n            return result\n\n        builtins.__import__ = tracked_import\n\n        try:\n            module_name = module.__name__\n            log.debug('Reloading module \"%s\"', module_name)\n\n            spec.loader.exec_module(module)\n        finally:\n            builtins.__import__ = original_import\n\n        return imported_modules\n\n    @staticmethod\n    def _reload_modules(modules: set[str], reloaded_modules_tracker: set[str]):\n        \"\"\"\n        Out of the given modules, reloads the ones that were not already imported.\n\n        We skip modules that are not user-defined (see `is_user_defined_module()`).\n        \"\"\"\n        for mod in modules:\n            if mod in reloaded_modules_tracker:\n                continue\n\n            if not ModuleLoader._is_user_defined_module(mod):\n                continue\n\n            module = sys.modules[mod]\n            ModuleLoader._deep_reload(module, reloaded_modules_tracker)\n\n            reloaded_modules_tracker.add(mod)\n\n    @staticmethod\n    def _is_user_defined_module(mod: str) -> bool:\n        \"\"\"\n        Returns whether the given module is user-defined or not.\n\n        A module is considered user-defined if\n        - it is not part of the standard library\n        - AND it is not an external library (site-packages or dist-packages)\n        \"\"\"\n        if mod not in sys.modules:\n            return False\n\n        if mod in sys.builtin_module_names:\n            return False\n\n        module = sys.modules[mod]\n        module_path = getattr(module, \"__file__\", None)\n        if module_path is None:\n            return False\n        module_path = os.path.abspath(module_path)\n\n        # External libraries (site-packages or dist-packages), e.g. numpy\n        if \"site-packages\" in module_path or \"dist-packages\" in module_path:\n            return False\n\n        # Standard lib\n        standard_lib_path = sysconfig.get_path(\"stdlib\")\n        if module_path.startswith(standard_lib_path):\n            return False\n\n        return True\n\n    @staticmethod\n    def _deep_reload(module: Module, reloaded_modules_tracker: set[str]):\n        \"\"\"\n        Recursively reloads modules imported by the given module.\n\n        Only user-defined modules are reloaded, see `is_user_defined_module()`.\n        \"\"\"\n        ignore_manimlib_modules = manim_config.ignore_manimlib_modules_on_reload\n        if ignore_manimlib_modules and module.__name__.startswith(\"manimlib\"):\n            return\n        if module.__name__.startswith(\"manimlib.config\"):\n            # We don't want to reload global manim_config\n            return\n\n        if not hasattr(module, \"__dict__\"):\n            return\n\n        # Prevent reloading the same module multiple times\n        if module.__name__ in reloaded_modules_tracker:\n            return\n        reloaded_modules_tracker.add(module.__name__)\n\n        # Recurse for all imported modules\n        for _attr_name, attr_value in module.__dict__.items():\n            if isinstance(attr_value, Module):\n                if ModuleLoader._is_user_defined_module(attr_value.__name__):\n                    ModuleLoader._deep_reload(attr_value, reloaded_modules_tracker)\n\n            # Also reload modules that are part of a class or function\n            # e.g. when importing `from custom_module import CustomClass`\n            elif hasattr(attr_value, \"__module__\"):\n                attr_module_name = attr_value.__module__\n                if ModuleLoader._is_user_defined_module(attr_module_name):\n                    attr_module = sys.modules[attr_module_name]\n                    ModuleLoader._deep_reload(attr_module, reloaded_modules_tracker)\n\n        # Reload\n        log.debug('Reloading module \"%s\"', module.__name__)\n        importlib.reload(module)\n"
  },
  {
    "path": "manimlib/scene/__init__.py",
    "content": ""
  },
  {
    "path": "manimlib/scene/interactive_scene.py",
    "content": "from __future__ import annotations\n\nimport itertools as it\nimport numpy as np\nimport pyperclip\nfrom IPython.core.getipython import get_ipython\nfrom pyglet.window import key as PygletWindowKeys\n\nfrom manimlib.animation.fading import FadeIn\nfrom manimlib.config import manim_config\nfrom manimlib.constants import DL, DOWN, DR, LEFT, ORIGIN, RIGHT, UL, UP, UR\nfrom manimlib.constants import FRAME_WIDTH, FRAME_HEIGHT, SMALL_BUFF\nfrom manimlib.constants import PI\nfrom manimlib.constants import DEG\nfrom manimlib.constants import MANIM_COLORS, WHITE, GREY_A, GREY_C\nfrom manimlib.mobject.geometry import Line\nfrom manimlib.mobject.geometry import Rectangle\nfrom manimlib.mobject.geometry import Square\nfrom manimlib.mobject.mobject import Group\nfrom manimlib.mobject.mobject import Mobject\nfrom manimlib.mobject.numbers import DecimalNumber\nfrom manimlib.mobject.svg.tex_mobject import Tex\nfrom manimlib.mobject.svg.text_mobject import Text\nfrom manimlib.mobject.types.dot_cloud import DotCloud\nfrom manimlib.mobject.types.vectorized_mobject import VGroup\nfrom manimlib.mobject.types.vectorized_mobject import VHighlight\nfrom manimlib.mobject.types.vectorized_mobject import VMobject\nfrom manimlib.scene.scene import Scene\nfrom manimlib.scene.scene import SceneState\nfrom manimlib.utils.family_ops import extract_mobject_family_members\nfrom manimlib.utils.space_ops import get_norm\nfrom manimlib.utils.tex_file_writing import LatexError\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from manimlib.typing import Vect3\n\n\nSELECT_KEY = manim_config.key_bindings.select\nUNSELECT_KEY = manim_config.key_bindings.unselect\nGRAB_KEY = manim_config.key_bindings.grab\nX_GRAB_KEY = manim_config.key_bindings.x_grab\nY_GRAB_KEY = manim_config.key_bindings.y_grab\nZ_GRAB_KEY = manim_config.key_bindings.z_grab\nGRAB_KEYS = [GRAB_KEY, X_GRAB_KEY, Y_GRAB_KEY, Z_GRAB_KEY]\nRESIZE_KEY = manim_config.key_bindings.resize  # TODO\nCOLOR_KEY = manim_config.key_bindings.color\nINFORMATION_KEY = manim_config.key_bindings.information\nCURSOR_KEY = manim_config.key_bindings.cursor\n\n# For keyboard interactions\n\nARROW_SYMBOLS: list[int] = [\n    PygletWindowKeys.LEFT,\n    PygletWindowKeys.UP,\n    PygletWindowKeys.RIGHT,\n    PygletWindowKeys.DOWN,\n]\n\nALL_MODIFIERS = PygletWindowKeys.MOD_CTRL | PygletWindowKeys.MOD_COMMAND | PygletWindowKeys.MOD_SHIFT\n\n# Note, a lot of the functionality here is still buggy and very much a work in progress.\n\n\nclass InteractiveScene(Scene):\n    \"\"\"\n    To select mobjects on screen, hold ctrl and move the mouse to highlight a region,\n    or just tap ctrl to select the mobject under the cursor.\n\n    Pressing command + t will toggle between modes where you either select top level\n    mobjects part of the scene, or low level pieces.\n\n    Hold 'g' to grab the selection and move it around\n    Hold 'h' to drag it constrained in the horizontal direction\n    Hold 'v' to drag it constrained in the vertical direction\n    Hold 't' to resize selection, adding 'shift' to resize with respect to a corner\n\n    Command + 'c' copies the ids of selections to clipboard\n    Command + 'v' will paste either:\n        - The copied mobject\n        - A Tex mobject based on copied LaTeX\n        - A Text mobject based on copied Text\n    Command + 'z' restores selection back to its original state\n    Command + 's' saves the selected mobjects to file\n    \"\"\"\n    corner_dot_config = dict(\n        color=WHITE,\n        radius=0.05,\n        glow_factor=2.0,\n    )\n    selection_rectangle_stroke_color = WHITE\n    selection_rectangle_stroke_width = 1.0\n    palette_colors = MANIM_COLORS\n    selection_nudge_size = 0.05\n    cursor_location_config = dict(\n        font_size=24,\n        fill_color=GREY_C,\n        num_decimal_places=3,\n    )\n    time_label_config = dict(\n        font_size=24,\n        fill_color=GREY_C,\n        num_decimal_places=1,\n    )\n    crosshair_width = 0.2\n    crosshair_style = dict(\n        stroke_color=GREY_A,\n        stroke_width=[3, 0, 3],\n    )\n\n    def setup(self):\n        self.selection = Group()\n        self.selection_highlight = self.get_selection_highlight()\n        self.selection_rectangle = self.get_selection_rectangle()\n        self.crosshair = self.get_crosshair()\n        self.information_label = self.get_information_label()\n        self.color_palette = self.get_color_palette()\n        self.unselectables = [\n            self.selection,\n            self.selection_highlight,\n            self.selection_rectangle,\n            self.crosshair,\n            self.information_label,\n            self.camera.frame\n        ]\n        self.select_top_level_mobs = True\n        self.regenerate_selection_search_set()\n\n        self.is_selecting = False\n        self.is_grabbing = False\n\n        self.add(self.selection_highlight)\n\n    def get_selection_rectangle(self):\n        rect = Rectangle(\n            stroke_color=self.selection_rectangle_stroke_color,\n            stroke_width=self.selection_rectangle_stroke_width,\n        )\n        rect.fix_in_frame()\n        rect.fixed_corner = ORIGIN\n        rect.add_updater(self.update_selection_rectangle)\n        return rect\n\n    def update_selection_rectangle(self, rect: Rectangle):\n        p1 = rect.fixed_corner\n        p2 = self.frame.to_fixed_frame_point(self.mouse_point.get_center())\n        rect.set_points_as_corners([\n            p1, np.array([p2[0], p1[1], 0]),\n            p2, np.array([p1[0], p2[1], 0]),\n            p1,\n        ])\n        return rect\n\n    def get_selection_highlight(self):\n        result = Group()\n        result.tracked_mobjects = []\n        result.add_updater(self.update_selection_highlight)\n        return result\n\n    def update_selection_highlight(self, highlight: Mobject):\n        if set(highlight.tracked_mobjects) == set(self.selection):\n            return\n\n        # Otherwise, refresh contents of highlight\n        highlight.tracked_mobjects = list(self.selection)\n        highlight.set_submobjects([\n            self.get_highlight(mob)\n            for mob in self.selection\n        ])\n        try:\n            index = min((\n                i for i, mob in enumerate(self.mobjects)\n                for sm in self.selection\n                if sm in mob.get_family()\n            ))\n            self.mobjects.remove(highlight)\n            self.mobjects.insert(index - 1, highlight)\n        except ValueError:\n            pass\n\n    def get_crosshair(self):\n        lines = VMobject().replicate(2)\n        lines[0].set_points([LEFT, ORIGIN, RIGHT])\n        lines[1].set_points([UP, ORIGIN, DOWN])\n        crosshair = VGroup(*lines)\n\n        crosshair.set_width(self.crosshair_width)\n        crosshair.set_style(**self.crosshair_style)\n        crosshair.set_animating_status(True)\n        crosshair.fix_in_frame()\n        return crosshair\n\n    def get_color_palette(self):\n        palette = VGroup(*(\n            Square(fill_color=color, fill_opacity=1, side_length=1)\n            for color in self.palette_colors\n        ))\n        palette.set_stroke(width=0)\n        palette.arrange(RIGHT, buff=0.5)\n        palette.set_width(FRAME_WIDTH - 0.5)\n        palette.to_edge(DOWN, buff=SMALL_BUFF)\n        palette.fix_in_frame()\n        return palette\n\n    def get_information_label(self):\n        loc_label = VGroup(*(\n            DecimalNumber(**self.cursor_location_config)\n            for n in range(3)\n        ))\n\n        def update_coords(loc_label):\n            for mob, coord in zip(loc_label, self.mouse_point.get_location()):\n                mob.set_value(coord)\n            loc_label.arrange(RIGHT, buff=loc_label.get_height())\n            loc_label.to_corner(DR, buff=SMALL_BUFF)\n            loc_label.fix_in_frame()\n            return loc_label\n\n        loc_label.add_updater(update_coords)\n\n        time_label = DecimalNumber(0, **self.time_label_config)\n        time_label.to_corner(DL, buff=SMALL_BUFF)\n        time_label.fix_in_frame()\n        time_label.add_updater(lambda m, dt: m.increment_value(dt))\n\n        return VGroup(loc_label, time_label)\n\n    # Overrides\n    def get_state(self):\n        return SceneState(self, ignore=[\n            self.selection_highlight,\n            self.selection_rectangle,\n            self.crosshair,\n        ])\n\n    def restore_state(self, scene_state: SceneState):\n        super().restore_state(scene_state)\n        self.mobjects.insert(0, self.selection_highlight)\n\n    def add(self, *mobjects: Mobject):\n        super().add(*mobjects)\n        self.regenerate_selection_search_set()\n\n    def remove(self, *mobjects: Mobject):\n        super().remove(*mobjects)\n        self.regenerate_selection_search_set()\n\n    def remove_all_except(self, *mobjects_to_keep : Mobject):\n        super().remove_all_except(*mobjects_to_keep)\n        self.regenerate_selection_search_set()\n\n    # Related to selection\n\n    def toggle_selection_mode(self):\n        self.select_top_level_mobs = not self.select_top_level_mobs\n        self.refresh_selection_scope()\n        self.regenerate_selection_search_set()\n\n    def get_selection_search_set(self) -> list[Mobject]:\n        return self.selection_search_set\n\n    def regenerate_selection_search_set(self):\n        selectable = list(filter(\n            lambda m: m not in self.unselectables,\n            self.mobjects\n        ))\n        if self.select_top_level_mobs:\n            self.selection_search_set = selectable\n        else:\n            self.selection_search_set = [\n                submob\n                for mob in selectable\n                for submob in mob.family_members_with_points()\n            ]\n\n    def refresh_selection_scope(self):\n        curr = list(self.selection)\n        if self.select_top_level_mobs:\n            self.selection.set_submobjects([\n                mob\n                for mob in self.mobjects\n                if any(sm in mob.get_family() for sm in curr)\n            ])\n            self.selection.refresh_bounding_box(recurse_down=True)\n        else:\n            self.selection.set_submobjects(\n                extract_mobject_family_members(\n                    curr, exclude_pointless=True,\n                )\n            )\n\n    def get_corner_dots(self, mobject: Mobject) -> Mobject:\n        dots = DotCloud(**self.corner_dot_config)\n        radius = float(self.corner_dot_config[\"radius\"])\n        if mobject.get_depth() < 1e-2:\n            vects = [DL, UL, UR, DR]\n        else:\n            vects = np.array(list(it.product(*3 * [[-1, 1]])))\n        dots.add_updater(lambda d: d.set_points([\n            mobject.get_corner(v) + v * radius\n            for v in vects\n        ]))\n        return dots\n\n    def get_highlight(self, mobject: Mobject) -> Mobject:\n        if isinstance(mobject, VMobject) and mobject.has_points() and not self.select_top_level_mobs:\n            length = max([mobject.get_height(), mobject.get_width()])\n            result = VHighlight(\n                mobject,\n                max_stroke_addition=min([50 * length, 10]),\n            )\n            result.add_updater(lambda m: m.replace(mobject, stretch=True))\n            return result\n        elif isinstance(mobject, DotCloud):\n            return Mobject()\n        else:\n            return self.get_corner_dots(mobject)\n\n    def add_to_selection(self, *mobjects: Mobject):\n        mobs = list(filter(\n            lambda m: m not in self.unselectables and m not in self.selection,\n            mobjects\n        ))\n        if len(mobs) == 0:\n            return\n        self.selection.add(*mobs)\n        for mob in mobs:\n            mob.set_animating_status(True)\n\n    def toggle_from_selection(self, *mobjects: Mobject):\n        for mob in mobjects:\n            if mob in self.selection:\n                self.selection.remove(mob)\n                mob.set_animating_status(False)\n                mob.refresh_bounding_box()\n            else:\n                self.add_to_selection(mob)\n\n    def clear_selection(self):\n        for mob in self.selection:\n            mob.set_animating_status(False)\n            mob.refresh_bounding_box()\n        self.selection.set_submobjects([])\n\n    def disable_interaction(self, *mobjects: Mobject):\n        for mob in mobjects:\n            for sm in mob.get_family():\n                self.unselectables.append(sm)\n        self.regenerate_selection_search_set()\n\n    def enable_interaction(self, *mobjects: Mobject):\n        for mob in mobjects:\n            for sm in mob.get_family():\n                if sm in self.unselectables:\n                    self.unselectables.remove(sm)\n\n    # Functions for keyboard actions\n\n    def copy_selection(self):\n        names = []\n        shell = get_ipython()\n        for mob in self.selection:\n            name = str(id(mob))\n            if shell is None:\n                continue\n            for key, value in shell.user_ns.items():\n                if mob is value:\n                    name = key\n            names.append(name)\n        pyperclip.copy(\", \".join(names))\n\n    def paste_selection(self):\n        clipboard_str = pyperclip.paste()\n        # Try pasting a mobject\n        try:\n            ids = map(int, clipboard_str.split(\",\"))\n            mobs = map(self.id_to_mobject, ids)\n            mob_copies = [m.copy() for m in mobs if m is not None]\n            self.clear_selection()\n            self.play(*(\n                FadeIn(mc, run_time=0.5, scale=1.5)\n                for mc in mob_copies\n            ))\n            self.add_to_selection(*mob_copies)\n            return\n        except ValueError:\n            pass\n        # Otherwise, treat as tex or text\n        if set(\"\\\\^=+\").intersection(clipboard_str):  # Proxy to text for LaTeX\n            try:\n                new_mob = Tex(clipboard_str)\n            except LatexError:\n                return\n        else:\n            new_mob = Text(clipboard_str)\n        self.clear_selection()\n        self.add(new_mob)\n        self.add_to_selection(new_mob)\n\n    def delete_selection(self):\n        self.remove(*self.selection)\n        self.clear_selection()\n\n    def enable_selection(self):\n        self.is_selecting = True\n        self.add(self.selection_rectangle)\n        self.selection_rectangle.fixed_corner = self.frame.to_fixed_frame_point(\n            self.mouse_point.get_center()\n        )\n\n    def gather_new_selection(self):\n        self.is_selecting = False\n        if self.selection_rectangle in self.mobjects:\n            self.remove(self.selection_rectangle)\n            additions = []\n            for mob in reversed(self.get_selection_search_set()):\n                if self.selection_rectangle.is_touching(mob):\n                    additions.append(mob)\n                    if self.selection_rectangle.get_arc_length() < 1e-2:\n                        break\n            self.toggle_from_selection(*additions)\n\n    def prepare_grab(self):\n        mp = self.mouse_point.get_center()\n        self.mouse_to_selection = mp - self.selection.get_center()\n        self.is_grabbing = True\n\n    def prepare_resizing(self, about_corner=False):\n        center = self.selection.get_center()\n        mp = self.mouse_point.get_center()\n        if about_corner:\n            self.scale_about_point = self.selection.get_corner(center - mp)\n        else:\n            self.scale_about_point = center\n        self.scale_ref_vect = mp - self.scale_about_point\n        self.scale_ref_width = self.selection.get_width()\n        self.scale_ref_height = self.selection.get_height()\n\n    def toggle_color_palette(self):\n        if len(self.selection) == 0:\n            return\n        if self.color_palette not in self.mobjects:\n            self.save_state()\n            self.add(self.color_palette)\n        else:\n            self.remove(self.color_palette)\n\n    def display_information(self, show=True):\n        if show:\n            self.add(self.information_label)\n        else:\n            self.remove(self.information_label)\n\n    def group_selection(self):\n        group = self.get_group(*self.selection)\n        self.add(group)\n        self.clear_selection()\n        self.add_to_selection(group)\n\n    def ungroup_selection(self):\n        pieces = []\n        for mob in list(self.selection):\n            self.remove(mob)\n            pieces.extend(list(mob))\n        self.clear_selection()\n        self.add(*pieces)\n        self.add_to_selection(*pieces)\n\n    def nudge_selection(self, vect: np.ndarray, large: bool = False):\n        nudge = self.selection_nudge_size\n        if large:\n            nudge *= 10\n        self.selection.shift(nudge * vect)\n\n    # Key actions\n    def on_key_press(self, symbol: int, modifiers: int) -> None:\n        super().on_key_press(symbol, modifiers)\n        try:\n            char = chr(symbol)\n        except OverflowError:\n            return\n        if char == SELECT_KEY and (modifiers & ALL_MODIFIERS) == 0:\n            self.enable_selection()\n        if char == UNSELECT_KEY:\n            self.clear_selection()\n        elif char in GRAB_KEYS and (modifiers & ALL_MODIFIERS) == 0:\n            self.prepare_grab()\n        elif char == RESIZE_KEY and (modifiers & PygletWindowKeys.MOD_SHIFT):\n            self.prepare_resizing(about_corner=((modifiers & PygletWindowKeys.MOD_SHIFT) > 0))\n        elif symbol == PygletWindowKeys.LSHIFT:\n            if self.window.is_key_pressed(ord(\"t\")):\n                self.prepare_resizing(about_corner=True)\n        elif char == COLOR_KEY and (modifiers & ALL_MODIFIERS) == 0:\n            self.toggle_color_palette()\n        elif char == INFORMATION_KEY and (modifiers & ALL_MODIFIERS) == 0:\n            self.display_information()\n        elif char == \"c\" and (modifiers & (PygletWindowKeys.MOD_COMMAND | PygletWindowKeys.MOD_CTRL)):\n            self.copy_selection()\n        elif char == \"v\" and (modifiers & (PygletWindowKeys.MOD_COMMAND | PygletWindowKeys.MOD_CTRL)):\n            self.paste_selection()\n        elif char == \"x\" and (modifiers & (PygletWindowKeys.MOD_COMMAND | PygletWindowKeys.MOD_CTRL)):\n            self.copy_selection()\n            self.delete_selection()\n        elif symbol == PygletWindowKeys.BACKSPACE:\n            self.delete_selection()\n        elif char == \"a\" and (modifiers & (PygletWindowKeys.MOD_COMMAND | PygletWindowKeys.MOD_CTRL)):\n            self.clear_selection()\n            self.add_to_selection(*self.mobjects)\n        elif char == \"g\" and (modifiers & (PygletWindowKeys.MOD_COMMAND | PygletWindowKeys.MOD_CTRL)):\n            self.group_selection()\n        elif char == \"g\" and (modifiers & (PygletWindowKeys.MOD_COMMAND | PygletWindowKeys.MOD_CTRL | PygletWindowKeys.MOD_SHIFT)):\n            self.ungroup_selection()\n        elif char == \"t\" and (modifiers & (PygletWindowKeys.MOD_COMMAND | PygletWindowKeys.MOD_CTRL)):\n            self.toggle_selection_mode()\n        elif char == \"d\" and (modifiers & PygletWindowKeys.MOD_SHIFT):\n            self.copy_frame_positioning()\n        elif char == \"c\" and (modifiers & PygletWindowKeys.MOD_SHIFT):\n            self.copy_cursor_position()\n        elif symbol in ARROW_SYMBOLS:\n            self.nudge_selection(\n                vect=[LEFT, UP, RIGHT, DOWN][ARROW_SYMBOLS.index(symbol)],\n                large=(modifiers & PygletWindowKeys.MOD_SHIFT),\n            )\n        # Adding crosshair\n        if char == CURSOR_KEY:\n            if self.crosshair in self.mobjects:\n                self.remove(self.crosshair)\n            else:\n                self.add(self.crosshair)\n        if char == SELECT_KEY:\n            self.add(self.crosshair)\n\n        # Conditions for saving state\n        if char in [GRAB_KEY, X_GRAB_KEY, Y_GRAB_KEY, Z_GRAB_KEY, RESIZE_KEY]:\n            self.save_state()\n\n    def on_key_release(self, symbol: int, modifiers: int) -> None:\n        super().on_key_release(symbol, modifiers)\n        if chr(symbol) == SELECT_KEY:\n            self.gather_new_selection()\n        if chr(symbol) in GRAB_KEYS:\n            self.is_grabbing = False\n        elif chr(symbol) == INFORMATION_KEY:\n            self.display_information(False)\n        elif symbol == PygletWindowKeys.LSHIFT and self.window.is_key_pressed(ord(RESIZE_KEY)):\n            self.prepare_resizing(about_corner=False)\n\n    # Mouse actions\n    def handle_grabbing(self, point: Vect3):\n        diff = point - self.mouse_to_selection\n        if self.window.is_key_pressed(ord(GRAB_KEY)):\n            self.selection.move_to(diff)\n        elif self.window.is_key_pressed(ord(X_GRAB_KEY)):\n            self.selection.set_x(diff[0])\n        elif self.window.is_key_pressed(ord(Y_GRAB_KEY)):\n            self.selection.set_y(diff[1])\n        elif self.window.is_key_pressed(ord(Z_GRAB_KEY)):\n            self.selection.set_z(diff[2])\n\n    def handle_resizing(self, point: Vect3):\n        if not hasattr(self, \"scale_about_point\"):\n            return\n        vect = point - self.scale_about_point\n        if self.window.is_key_pressed(PygletWindowKeys.LCTRL):\n            for i in (0, 1):\n                scalar = vect[i] / self.scale_ref_vect[i]\n                self.selection.rescale_to_fit(\n                    scalar * [self.scale_ref_width, self.scale_ref_height][i],\n                    dim=i,\n                    about_point=self.scale_about_point,\n                    stretch=True,\n                )\n        else:\n            scalar = get_norm(vect) / get_norm(self.scale_ref_vect)\n            self.selection.set_width(\n                scalar * self.scale_ref_width,\n                about_point=self.scale_about_point\n            )\n\n    def handle_sweeping_selection(self, point: Vect3):\n        mob = self.point_to_mobject(\n            point,\n            search_set=self.get_selection_search_set(),\n            buff=SMALL_BUFF\n        )\n        if mob is not None:\n            self.add_to_selection(mob)\n\n    def choose_color(self, point: Vect3):\n        # Search through all mobject on the screen, not just the palette\n        to_search = [\n            sm\n            for mobject in self.mobjects\n            for sm in mobject.family_members_with_points()\n            if mobject not in self.unselectables\n        ]\n        mob = self.point_to_mobject(point, to_search)\n        if mob is not None:\n            self.selection.set_color(mob.get_color())\n        self.remove(self.color_palette)\n\n    def on_mouse_motion(self, point: Vect3, d_point: Vect3) -> None:\n        super().on_mouse_motion(point, d_point)\n        self.crosshair.move_to(self.frame.to_fixed_frame_point(point))\n        if self.is_grabbing:\n            self.handle_grabbing(point)\n        elif self.window.is_key_pressed(ord(RESIZE_KEY)):\n            self.handle_resizing(point)\n        elif self.window.is_key_pressed(ord(SELECT_KEY)) and self.window.is_key_pressed(PygletWindowKeys.LSHIFT):\n            self.handle_sweeping_selection(point)\n\n    def on_mouse_drag(\n        self,\n        point: Vect3,\n        d_point: Vect3,\n        buttons: int,\n        modifiers: int\n    ) -> None:\n        super().on_mouse_drag(point, d_point, buttons, modifiers)\n        self.crosshair.move_to(self.frame.to_fixed_frame_point(point))\n\n    def on_mouse_release(self, point: Vect3, button: int, mods: int) -> None:\n        super().on_mouse_release(point, button, mods)\n        if self.color_palette in self.mobjects:\n            self.choose_color(point)\n        else:\n            self.clear_selection()\n\n    # Copying code to recreate state\n    def copy_frame_positioning(self):\n        frame = self.frame\n        center = frame.get_center()\n        height = frame.get_height()\n        angles = frame.get_euler_angles()\n\n        call = f\"reorient(\"\n        theta, phi, gamma = (angles / DEG).astype(int)\n        call += f\"{theta}, {phi}, {gamma}\"\n        if any(center != 0):\n            call += f\", {tuple(np.round(center, 2))}\"\n        if height != FRAME_HEIGHT:\n            call += \", {:.2f}\".format(height)\n        call += \")\"\n        pyperclip.copy(call)\n\n    def copy_cursor_position(self):\n        pyperclip.copy(str(tuple(self.mouse_point.get_center().round(2))))\n"
  },
  {
    "path": "manimlib/scene/scene.py",
    "content": "from __future__ import annotations\n\nfrom collections import OrderedDict\nimport platform\nimport random\nimport time\nfrom functools import wraps\nfrom contextlib import contextmanager\nfrom contextlib import ExitStack\n\nimport numpy as np\nfrom tqdm.auto import tqdm as ProgressDisplay\nfrom pyglet.window import key as PygletWindowKeys\n\nfrom manimlib.animation.animation import prepare_animation\nfrom manimlib.camera.camera import Camera\nfrom manimlib.camera.camera_frame import CameraFrame\nfrom manimlib.config import manim_config\nfrom manimlib.event_handler import EVENT_DISPATCHER\nfrom manimlib.event_handler.event_type import EventType\nfrom manimlib.logger import log\nfrom manimlib.mobject.mobject import _AnimationBuilder\nfrom manimlib.mobject.mobject import Group\nfrom manimlib.mobject.mobject import Mobject\nfrom manimlib.mobject.mobject import Point\nfrom manimlib.mobject.types.vectorized_mobject import VGroup\nfrom manimlib.mobject.types.vectorized_mobject import VMobject\nfrom manimlib.scene.scene_embed import InteractiveSceneEmbed\nfrom manimlib.scene.scene_embed import CheckpointManager\nfrom manimlib.scene.scene_file_writer import SceneFileWriter\nfrom manimlib.utils.dict_ops import merge_dicts_recursively\nfrom manimlib.utils.family_ops import extract_mobject_family_members\nfrom manimlib.utils.family_ops import recursive_mobject_remove\nfrom manimlib.utils.iterables import batch_by_property\nfrom manimlib.utils.sounds import play_sound\nfrom manimlib.utils.color import color_to_rgba\nfrom manimlib.window import Window\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable, Iterable, TypeVar, Optional\n    from manimlib.typing import Vect3\n\n    T = TypeVar('T')\n\n    from PIL.Image import Image\n\n    from manimlib.animation.animation import Animation\n\n\nclass Scene(object):\n    random_seed: int = 0\n    pan_sensitivity: float = 0.5\n    scroll_sensitivity: float = 20\n    drag_to_pan: bool = True\n    max_num_saved_states: int = 50\n    default_camera_config: dict = dict()\n    default_file_writer_config: dict = dict()\n    samples = 0\n    # Euler angles, in degrees\n    default_frame_orientation = (0, 0)\n\n    def __init__(\n        self,\n        window: Optional[Window] = None,\n        camera_config: dict = dict(),\n        file_writer_config: dict = dict(),\n        skip_animations: bool = False,\n        always_update_mobjects: bool = False,\n        start_at_animation_number: int | None = None,\n        end_at_animation_number: int | None = None,\n        show_animation_progress: bool = False,\n        leave_progress_bars: bool = False,\n        preview_while_skipping: bool = True,\n        presenter_mode: bool = False,\n        default_wait_time: float = 1.0,\n    ):\n        self.skip_animations = skip_animations\n        self.always_update_mobjects = always_update_mobjects\n        self.start_at_animation_number = start_at_animation_number\n        self.end_at_animation_number = end_at_animation_number\n        self.show_animation_progress = show_animation_progress\n        self.leave_progress_bars = leave_progress_bars\n        self.preview_while_skipping = preview_while_skipping\n        self.presenter_mode = presenter_mode\n        self.default_wait_time = default_wait_time\n\n        self.camera_config = merge_dicts_recursively(\n            manim_config.camera,         # Global default\n            self.default_camera_config,  # Updated configuration that subclasses may specify\n            camera_config,               # Updated configuration from instantiation\n        )\n        self.file_writer_config = merge_dicts_recursively(\n            manim_config.file_writer,\n            self.default_file_writer_config,\n            file_writer_config,\n        )\n\n        self.window = window\n        if self.window:\n            self.window.init_for_scene(self)\n            # Make sure camera and Pyglet window sync\n            self.camera_config[\"fps\"] = 30\n\n        # Core state of the scene\n        self.camera: Camera = Camera(\n            window=self.window,\n            samples=self.samples,\n            **self.camera_config\n        )\n        self.frame: CameraFrame = self.camera.frame\n        self.frame.reorient(*self.default_frame_orientation)\n        self.frame.make_orientation_default()\n\n        self.file_writer = SceneFileWriter(self, **self.file_writer_config)\n        self.mobjects: list[Mobject] = [self.camera.frame]\n        self.render_groups: list[Mobject] = []\n        self.id_to_mobject_map: dict[int, Mobject] = dict()\n        self.num_plays: int = 0\n        self.time: float = 0\n        self.skip_time: float = 0\n        self.original_skipping_status: bool = self.skip_animations\n        self.undo_stack = []\n        self.redo_stack = []\n\n        if self.start_at_animation_number is not None:\n            self.skip_animations = True\n        if self.file_writer.has_progress_display():\n            self.show_animation_progress = False\n\n        # Items associated with interaction\n        self.mouse_point = Point()\n        self.mouse_drag_point = Point()\n        self.hold_on_wait = self.presenter_mode\n        self.quit_interaction = False\n\n        # Much nicer to work with deterministic scenes\n        if self.random_seed is not None:\n            random.seed(self.random_seed)\n            np.random.seed(self.random_seed)\n\n    def __str__(self) -> str:\n        return self.__class__.__name__\n\n    def get_window(self) -> Window | None:\n        return self.window\n\n    def run(self) -> None:\n        self.virtual_animation_start_time: float = 0\n        self.real_animation_start_time: float = time.time()\n        self.file_writer.begin()\n\n        self.setup()\n        try:\n            self.construct()\n            self.interact()\n        except EndScene:\n            pass\n        except KeyboardInterrupt:\n            # Get rid keyboard interupt symbols\n            print(\"\", end=\"\\r\")\n            self.file_writer.ended_with_interrupt = True\n        self.tear_down()\n\n    def setup(self) -> None:\n        \"\"\"\n        This is meant to be implement by any scenes which\n        are comonly subclassed, and have some common setup\n        involved before the construct method is called.\n        \"\"\"\n        pass\n\n    def construct(self) -> None:\n        # Where all the animation happens\n        # To be implemented in subclasses\n        pass\n\n    def tear_down(self) -> None:\n        self.stop_skipping()\n        self.file_writer.finish()\n        if self.window:\n            self.window.destroy()\n            self.window = None\n\n    def interact(self) -> None:\n        \"\"\"\n        If there is a window, enter a loop\n        which updates the frame while under\n        the hood calling the pyglet event loop\n        \"\"\"\n        if self.window is None:\n            return\n        log.info(\n            \"\\nTips: Using the keys `d`, `f`, or `z` \" +\n            \"you can interact with the scene. \" +\n            \"Press `command + q` or `esc` to quit\"\n        )\n        self.skip_animations = False\n        while not self.is_window_closing():\n            self.update_frame(1 / self.camera.fps)\n\n    def embed(\n        self,\n        close_scene_on_exit: bool = True,\n        show_animation_progress: bool = False,\n    ) -> None:\n        if not self.window:\n            # Embed is only relevant for interactive development with a Window\n            return\n        self.show_animation_progress = show_animation_progress\n        self.stop_skipping()\n        self.update_frame(force_draw=True)\n\n        InteractiveSceneEmbed(self).launch()\n\n        # End scene when exiting an embed\n        if close_scene_on_exit:\n            raise EndScene()\n\n    # Only these methods should touch the camera\n\n    def get_image(self) -> Image:\n        if self.window is not None:\n            self.camera.use_window_fbo(False)\n            self.camera.capture(*self.render_groups)\n        image = self.camera.get_image()\n        if self.window is not None:\n            self.camera.use_window_fbo(True)\n        return image\n\n    def show(self) -> None:\n        self.update_frame(force_draw=True)\n        self.get_image().show()\n\n    def update_frame(self, dt: float = 0, force_draw: bool = False) -> None:\n        self.increment_time(dt)\n        self.update_mobjects(dt)\n        if self.skip_animations and not force_draw:\n            return\n\n        if self.is_window_closing():\n            raise EndScene()\n\n        if self.window and dt == 0 and not self.window.has_undrawn_event() and not force_draw:\n            # In this case, there's no need for new rendering, but we\n            # shoudl still listen for new events\n            self.window._window.dispatch_events()\n            return\n\n        self.camera.capture(*self.render_groups)\n\n        if self.window and not self.skip_animations:\n            vt = self.time - self.virtual_animation_start_time\n            rt = time.time() - self.real_animation_start_time\n            time.sleep(max(vt - rt, 0))\n\n    def emit_frame(self) -> None:\n        if not self.skip_animations:\n            self.file_writer.write_frame(self.camera)\n\n    # Related to updating\n\n    def update_mobjects(self, dt: float) -> None:\n        for mobject in self.mobjects:\n            mobject.update(dt)\n\n    def should_update_mobjects(self) -> bool:\n        return self.always_update_mobjects or any(\n            mob.has_updaters() for mob in self.mobjects\n        )\n\n    # Related to time\n\n    def get_time(self) -> float:\n        return self.time\n\n    def increment_time(self, dt: float) -> None:\n        self.time += dt\n\n    # Related to internal mobject organization\n\n    def get_top_level_mobjects(self) -> list[Mobject]:\n        # Return only those which are not in the family\n        # of another mobject from the scene\n        mobjects = self.get_mobjects()\n        families = [m.get_family() for m in mobjects]\n\n        def is_top_level(mobject):\n            num_families = sum([\n                (mobject in family)\n                for family in families\n            ])\n            return num_families == 1\n        return list(filter(is_top_level, mobjects))\n\n    def get_mobject_family_members(self) -> list[Mobject]:\n        return extract_mobject_family_members(self.mobjects)\n\n    def assemble_render_groups(self):\n        \"\"\"\n        Rendering can be more efficient when mobjects of the\n        same type are grouped together, so this function creates\n        Groups of all clusters of adjacent Mobjects in the scene\n        \"\"\"\n        batches = batch_by_property(\n            self.mobjects,\n            lambda m: str(type(m)) + str(m.get_shader_wrapper(self.camera.ctx).get_id()) + str(m.z_index)\n        )\n\n        for group in self.render_groups:\n            group.clear()\n        self.render_groups = [\n            batch[0].get_group_class()(*batch)\n            for batch, key in batches\n        ]\n\n    @staticmethod\n    def affects_mobject_list(func: Callable[..., T]) -> Callable[..., T]:\n        @wraps(func)\n        def wrapper(self, *args, **kwargs):\n            func(self, *args, **kwargs)\n            self.assemble_render_groups()\n            return self\n        return wrapper\n\n    @affects_mobject_list\n    def add(self, *new_mobjects: Mobject):\n        \"\"\"\n        Mobjects will be displayed, from background to\n        foreground in the order with which they are added.\n        \"\"\"\n        self.remove(*new_mobjects)\n        self.mobjects += new_mobjects\n\n        # Reorder based on z_index\n        id_to_scene_order = {id(m): idx for idx, m in enumerate(self.mobjects)}\n        self.mobjects.sort(key=lambda m: (m.z_index, id_to_scene_order[id(m)]))\n\n        self.id_to_mobject_map.update({\n            id(sm): sm\n            for m in new_mobjects\n            for sm in m.get_family()\n        })\n        return self\n\n    def add_mobjects_among(self, values: Iterable):\n        \"\"\"\n        This is meant mostly for quick prototyping,\n        e.g. to add all mobjects defined up to a point,\n        call self.add_mobjects_among(locals().values())\n        \"\"\"\n        self.add(*filter(\n            lambda m: isinstance(m, Mobject),\n            values\n        ))\n        return self\n\n    @affects_mobject_list\n    def replace(self, mobject: Mobject, *replacements: Mobject):\n        if mobject in self.mobjects:\n            index = self.mobjects.index(mobject)\n            self.mobjects = [\n                *self.mobjects[:index],\n                *replacements,\n                *self.mobjects[index + 1:]\n            ]\n        return self\n\n    @affects_mobject_list\n    def remove(self, *mobjects_to_remove: Mobject):\n        \"\"\"\n        Removes anything in mobjects from scenes mobject list, but in the event that one\n        of the items to be removed is a member of the family of an item in mobject_list,\n        the other family members are added back into the list.\n\n        For example, if the scene includes Group(m1, m2, m3), and we call scene.remove(m1),\n        the desired behavior is for the scene to then include m2 and m3 (ungrouped).\n        \"\"\"\n        to_remove = set(extract_mobject_family_members(mobjects_to_remove))\n        new_mobjects, _ = recursive_mobject_remove(self.mobjects, to_remove)\n        self.mobjects = new_mobjects\n\n    @affects_mobject_list\n    def remove_all_except(self, *mobjects_to_keep : Mobject):\n        self.clear()\n        self.add(*mobjects_to_keep)\n\n    def bring_to_front(self, *mobjects: Mobject):\n        self.add(*mobjects)\n        return self\n\n    @affects_mobject_list\n    def bring_to_back(self, *mobjects: Mobject):\n        self.remove(*mobjects)\n        self.mobjects = list(mobjects) + self.mobjects\n        return self\n\n    @affects_mobject_list\n    def clear(self):\n        self.mobjects = []\n        return self\n\n    def get_mobjects(self) -> list[Mobject]:\n        return list(self.mobjects)\n\n    def get_mobject_copies(self) -> list[Mobject]:\n        return [m.copy() for m in self.mobjects]\n\n    def point_to_mobject(\n        self,\n        point: np.ndarray,\n        search_set: Iterable[Mobject] | None = None,\n        buff: float = 0\n    ) -> Mobject | None:\n        \"\"\"\n        E.g. if clicking on the scene, this returns the top layer mobject\n        under a given point\n        \"\"\"\n        if search_set is None:\n            search_set = self.mobjects\n        for mobject in reversed(search_set):\n            if mobject.is_point_touching(point, buff=buff):\n                return mobject\n        return None\n\n    def get_group(self, *mobjects):\n        if all(isinstance(m, VMobject) for m in mobjects):\n            return VGroup(*mobjects)\n        else:\n            return Group(*mobjects)\n\n    def id_to_mobject(self, id_value):\n        return self.id_to_mobject_map[id_value]\n\n    def ids_to_group(self, *id_values):\n        return self.get_group(*filter(\n            lambda x: x is not None,\n            map(self.id_to_mobject, id_values)\n        ))\n\n    def i2g(self, *id_values):\n        return self.ids_to_group(*id_values)\n\n    def i2m(self, id_value):\n        return self.id_to_mobject(id_value)\n\n    # Related to skipping\n\n    def update_skipping_status(self) -> None:\n        if self.start_at_animation_number is not None:\n            if self.num_plays == self.start_at_animation_number:\n                self.skip_time = self.time\n                if not self.original_skipping_status:\n                    self.stop_skipping()\n        if self.end_at_animation_number is not None:\n            if self.num_plays >= self.end_at_animation_number:\n                raise EndScene()\n\n    def stop_skipping(self) -> None:\n        self.virtual_animation_start_time = self.time\n        self.real_animation_start_time = time.time()\n        self.skip_animations = False\n\n    # Methods associated with running animations\n\n    def get_time_progression(\n        self,\n        run_time: float,\n        n_iterations: int | None = None,\n        desc: str = \"\",\n        override_skip_animations: bool = False\n    ) -> list[float] | np.ndarray | ProgressDisplay:\n        if self.skip_animations and not override_skip_animations:\n            return [run_time]\n\n        times = np.arange(0, run_time, 1 / self.camera.fps) + 1 / self.camera.fps\n\n        self.file_writer.set_progress_display_description(sub_desc=desc)\n\n        if self.show_animation_progress:\n            return ProgressDisplay(\n                times,\n                total=n_iterations,\n                leave=self.leave_progress_bars,\n                ascii=True if platform.system() == 'Windows' else None,\n                desc=desc,\n                bar_format=\"{l_bar} {n_fmt:3}/{total_fmt:3} {rate_fmt}{postfix}\",\n            )\n        else:\n            return times\n\n    def get_run_time(self, animations: Iterable[Animation]) -> float:\n        return np.max([animation.get_run_time() for animation in animations])\n\n    def get_animation_time_progression(\n        self,\n        animations: Iterable[Animation]\n    ) -> list[float] | np.ndarray | ProgressDisplay:\n        animations = list(animations)\n        run_time = self.get_run_time(animations)\n        description = f\"{self.num_plays} {animations[0]}\"\n        if len(animations) > 1:\n            description += \", etc.\"\n        time_progression = self.get_time_progression(run_time, desc=description)\n        return time_progression\n\n    def get_wait_time_progression(\n        self,\n        duration: float,\n        stop_condition: Callable[[], bool] | None = None\n    ) -> list[float] | np.ndarray | ProgressDisplay:\n        kw = {\"desc\": f\"{self.num_plays} Waiting\"}\n        if stop_condition is not None:\n            kw[\"n_iterations\"] = -1  # So it doesn't show % progress\n            kw[\"override_skip_animations\"] = True\n        return self.get_time_progression(duration, **kw)\n\n    def pre_play(self):\n        if self.presenter_mode and self.num_plays == 0:\n            self.hold_loop()\n\n        self.update_skipping_status()\n\n        if not self.skip_animations:\n            self.file_writer.begin_animation()\n\n        if self.window:\n            self.virtual_animation_start_time = self.time\n            self.real_animation_start_time = time.time()\n\n    def post_play(self):\n        if not self.skip_animations:\n            self.file_writer.end_animation()\n\n        if self.preview_while_skipping and self.skip_animations and self.window is not None:\n            # Show some quick frames along the way\n            self.update_frame(dt=0, force_draw=True)\n\n        self.num_plays += 1\n\n    def begin_animations(self, animations: Iterable[Animation]) -> None:\n        all_mobjects = set(self.get_mobject_family_members())\n        for animation in animations:\n            animation.begin()\n            # Anything animated that's not already in the\n            # scene gets added to the scene.  Note, for\n            # animated mobjects that are in the family of\n            # those on screen, this can result in a restructuring\n            # of the scene.mobjects list, which is usually desired.\n            if animation.mobject not in all_mobjects:\n                self.add(animation.mobject)\n                all_mobjects = all_mobjects.union(animation.mobject.get_family())\n\n    def progress_through_animations(self, animations: Iterable[Animation]) -> None:\n        last_t = 0\n        for t in self.get_animation_time_progression(animations):\n            dt = t - last_t\n            last_t = t\n            for animation in animations:\n                animation.update_mobjects(dt)\n                alpha = t / animation.run_time\n                animation.interpolate(alpha)\n            self.update_frame(dt)\n            self.emit_frame()\n\n    def finish_animations(self, animations: Iterable[Animation]) -> None:\n        for animation in animations:\n            animation.finish()\n            animation.clean_up_from_scene(self)\n        if self.skip_animations:\n            self.update_mobjects(self.get_run_time(animations))\n        else:\n            self.update_mobjects(0)\n\n    @affects_mobject_list\n    def play(\n        self,\n        *proto_animations: Animation | _AnimationBuilder,\n        run_time: float | None = None,\n        rate_func: Callable[[float], float] | None = None,\n        lag_ratio: float | None = None,\n    ) -> None:\n        if len(proto_animations) == 0:\n            log.warning(\"Called Scene.play with no animations\")\n            return\n        animations = list(map(prepare_animation, proto_animations))\n        for anim in animations:\n            anim.update_rate_info(run_time, rate_func, lag_ratio)\n        self.pre_play()\n        self.begin_animations(animations)\n        self.progress_through_animations(animations)\n        self.finish_animations(animations)\n        self.post_play()\n\n    def wait(\n        self,\n        duration: Optional[float] = None,\n        stop_condition: Callable[[], bool] = None,\n        note: str = None,\n        ignore_presenter_mode: bool = False\n    ):\n        if duration is None:\n            duration = self.default_wait_time\n        self.pre_play()\n        self.update_mobjects(dt=0)  # Any problems with this?\n        if self.presenter_mode and not self.skip_animations and not ignore_presenter_mode:\n            if note:\n                log.info(note)\n            self.hold_loop()\n        else:\n            time_progression = self.get_wait_time_progression(duration, stop_condition)\n            last_t = 0\n            for t in time_progression:\n                dt = t - last_t\n                last_t = t\n                self.update_frame(dt)\n                self.emit_frame()\n                if stop_condition is not None and stop_condition():\n                    break\n        self.post_play()\n\n    def hold_loop(self):\n        while self.hold_on_wait:\n            self.update_frame(dt=1 / self.camera.fps)\n        self.hold_on_wait = True\n\n    def wait_until(\n        self,\n        stop_condition: Callable[[], bool],\n        max_time: float = 60\n    ):\n        self.wait(max_time, stop_condition=stop_condition)\n\n    def force_skipping(self):\n        self.original_skipping_status = self.skip_animations\n        self.skip_animations = True\n        return self\n\n    def revert_to_original_skipping_status(self):\n        if hasattr(self, \"original_skipping_status\"):\n            self.skip_animations = self.original_skipping_status\n        return self\n\n    def add_sound(\n        self,\n        sound_file: str,\n        time_offset: float = 0,\n        gain: float | None = None,\n        gain_to_background: float | None = None\n    ):\n        if self.skip_animations:\n            return\n        time = self.get_time() + time_offset\n        self.file_writer.add_sound(sound_file, time, gain, gain_to_background)\n\n    # Helpers for interactive development\n\n    def get_state(self) -> SceneState:\n        return SceneState(self)\n\n    @affects_mobject_list\n    def restore_state(self, scene_state: SceneState):\n        scene_state.restore_scene(self)\n\n    def save_state(self) -> None:\n        state = self.get_state()\n        if self.undo_stack and state.mobjects_match(self.undo_stack[-1]):\n            return\n        self.redo_stack = []\n        self.undo_stack.append(state)\n        if len(self.undo_stack) > self.max_num_saved_states:\n            self.undo_stack.pop(0)\n\n    def undo(self):\n        if self.undo_stack:\n            self.redo_stack.append(self.get_state())\n            self.restore_state(self.undo_stack.pop())\n\n    def redo(self):\n        if self.redo_stack:\n            self.undo_stack.append(self.get_state())\n            self.restore_state(self.redo_stack.pop())\n\n    @contextmanager\n    def temp_skip(self):\n        prev_status = self.skip_animations\n        self.skip_animations = True\n        try:\n            yield\n        finally:\n            if not prev_status:\n                self.stop_skipping()\n\n    @contextmanager\n    def temp_progress_bar(self):\n        prev_progress = self.show_animation_progress\n        self.show_animation_progress = True\n        try:\n            yield\n        finally:\n            self.show_animation_progress = prev_progress\n\n    @contextmanager\n    def temp_record(self):\n        self.camera.use_window_fbo(False)\n        self.file_writer.begin_insert()\n        try:\n            yield\n        finally:\n            self.file_writer.end_insert()\n            self.camera.use_window_fbo(True)\n\n    def temp_config_change(self, skip=False, record=False, progress_bar=False):\n        stack = ExitStack()\n        if skip:\n            stack.enter_context(self.temp_skip())\n        if record:\n            stack.enter_context(self.temp_record())\n        if progress_bar:\n            stack.enter_context(self.temp_progress_bar())\n        return stack\n\n    def is_window_closing(self):\n        return self.window and (self.window.is_closing or self.quit_interaction)\n\n    # Event handling\n    def set_floor_plane(self, plane: str = \"xy\"):\n        if plane == \"xy\":\n            self.frame.set_euler_axes(\"zxz\")\n        elif plane == \"xz\":\n            self.frame.set_euler_axes(\"zxy\")\n        else:\n            raise Exception(\"Only `xz` and `xy` are valid floor planes\")\n\n    def on_mouse_motion(\n        self,\n        point: Vect3,\n        d_point: Vect3\n    ) -> None:\n        assert self.window is not None\n        self.mouse_point.move_to(point)\n\n        event_data = {\"point\": point, \"d_point\": d_point}\n        propagate_event = EVENT_DISPATCHER.dispatch(EventType.MouseMotionEvent, **event_data)\n        if propagate_event is not None and propagate_event is False:\n            return\n\n        frame = self.camera.frame\n        # Handle perspective changes\n        if self.window.is_key_pressed(ord(manim_config.key_bindings.pan_3d)):\n            ff_d_point = frame.to_fixed_frame_point(d_point, relative=True)\n            ff_d_point *= self.pan_sensitivity\n            frame.increment_theta(-ff_d_point[0])\n            frame.increment_phi(ff_d_point[1])\n        # Handle frame movements\n        elif self.window.is_key_pressed(ord(manim_config.key_bindings.pan)):\n            frame.shift(-d_point)\n\n    def on_mouse_drag(\n        self,\n        point: Vect3,\n        d_point: Vect3,\n        buttons: int,\n        modifiers: int\n    ) -> None:\n        self.mouse_drag_point.move_to(point)\n        if self.drag_to_pan:\n            self.frame.shift(-d_point)\n\n        event_data = {\"point\": point, \"d_point\": d_point, \"buttons\": buttons, \"modifiers\": modifiers}\n        propagate_event = EVENT_DISPATCHER.dispatch(EventType.MouseDragEvent, **event_data)\n        if propagate_event is not None and propagate_event is False:\n            return\n\n    def on_mouse_press(\n        self,\n        point: Vect3,\n        button: int,\n        mods: int\n    ) -> None:\n        self.mouse_drag_point.move_to(point)\n        event_data = {\"point\": point, \"button\": button, \"mods\": mods}\n        propagate_event = EVENT_DISPATCHER.dispatch(EventType.MousePressEvent, **event_data)\n        if propagate_event is not None and propagate_event is False:\n            return\n\n    def on_mouse_release(\n        self,\n        point: Vect3,\n        button: int,\n        mods: int\n    ) -> None:\n        event_data = {\"point\": point, \"button\": button, \"mods\": mods}\n        propagate_event = EVENT_DISPATCHER.dispatch(EventType.MouseReleaseEvent, **event_data)\n        if propagate_event is not None and propagate_event is False:\n            return\n\n    def on_mouse_scroll(\n        self,\n        point: Vect3,\n        offset: Vect3,\n        x_pixel_offset: float,\n        y_pixel_offset: float\n    ) -> None:\n        event_data = {\"point\": point, \"offset\": offset}\n        propagate_event = EVENT_DISPATCHER.dispatch(EventType.MouseScrollEvent, **event_data)\n        if propagate_event is not None and propagate_event is False:\n            return\n\n        rel_offset = y_pixel_offset / self.camera.get_pixel_height()\n        self.frame.scale(\n            1 - self.scroll_sensitivity * rel_offset,\n            about_point=point\n        )\n\n    def on_key_release(\n        self,\n        symbol: int,\n        modifiers: int\n    ) -> None:\n        event_data = {\"symbol\": symbol, \"modifiers\": modifiers}\n        propagate_event = EVENT_DISPATCHER.dispatch(EventType.KeyReleaseEvent, **event_data)\n        if propagate_event is not None and propagate_event is False:\n            return\n\n    def on_key_press(\n        self,\n        symbol: int,\n        modifiers: int\n    ) -> None:\n        try:\n            char = chr(symbol)\n        except OverflowError:\n            log.warning(\"The value of the pressed key is too large.\")\n            return\n\n        event_data = {\"symbol\": symbol, \"modifiers\": modifiers}\n        propagate_event = EVENT_DISPATCHER.dispatch(EventType.KeyPressEvent, **event_data)\n        if propagate_event is not None and propagate_event is False:\n            return\n\n        if char == manim_config.key_bindings.reset:\n            self.play(self.camera.frame.animate.to_default_state())\n        elif char == \"z\" and (modifiers & (PygletWindowKeys.MOD_COMMAND | PygletWindowKeys.MOD_CTRL)):\n            self.undo()\n        elif char == \"z\" and (modifiers & (PygletWindowKeys.MOD_COMMAND | PygletWindowKeys.MOD_CTRL | PygletWindowKeys.MOD_SHIFT)):\n            self.redo()\n        # command + q\n        elif char == manim_config.key_bindings.quit and (modifiers & (PygletWindowKeys.MOD_COMMAND | PygletWindowKeys.MOD_CTRL)):\n            self.quit_interaction = True\n        # Space or right arrow\n        elif char == \" \" or symbol == PygletWindowKeys.RIGHT:\n            self.hold_on_wait = False\n\n    def on_resize(self, width: int, height: int) -> None:\n        pass\n\n    def on_show(self) -> None:\n        pass\n\n    def on_hide(self) -> None:\n        pass\n\n    def on_close(self) -> None:\n        pass\n\n    def focus(self) -> None:\n        \"\"\"\n        Puts focus on the ManimGL window.\n        \"\"\"\n        if not self.window:\n            return\n        self.window.focus()\n\n    def set_background_color(self, background_color, background_opacity=1) -> None:\n        self.camera.background_rgba = list(color_to_rgba(\n            background_color, background_opacity\n        ))\n\n\nclass SceneState():\n    def __init__(self, scene: Scene, ignore: list[Mobject] | None = None):\n        self.time = scene.time\n        self.num_plays = scene.num_plays\n        self.mobjects_to_copies = OrderedDict.fromkeys(scene.mobjects)\n        if ignore:\n            for mob in ignore:\n                self.mobjects_to_copies.pop(mob, None)\n\n        last_m2c = scene.undo_stack[-1].mobjects_to_copies if scene.undo_stack else dict()\n        for mob in self.mobjects_to_copies:\n            # If it hasn't changed since the last state, just point to the\n            # same copy as before\n            if mob in last_m2c and last_m2c[mob].looks_identical(mob):\n                self.mobjects_to_copies[mob] = last_m2c[mob]\n            else:\n                self.mobjects_to_copies[mob] = mob.copy()\n\n    def __eq__(self, state: SceneState):\n        return all((\n            self.time == state.time,\n            self.num_plays == state.num_plays,\n            self.mobjects_to_copies == state.mobjects_to_copies\n        ))\n\n    def mobjects_match(self, state: SceneState):\n        return self.mobjects_to_copies == state.mobjects_to_copies\n\n    def n_changes(self, state: SceneState):\n        m2c = state.mobjects_to_copies\n        return sum(\n            1 - int(mob in m2c and mob.looks_identical(m2c[mob]))\n            for mob in self.mobjects_to_copies\n        )\n\n    def restore_scene(self, scene: Scene):\n        scene.time = self.time\n        scene.num_plays = self.num_plays\n        scene.mobjects = [\n            mob.become(mob_copy, match_updaters=True)\n            for mob, mob_copy in self.mobjects_to_copies.items()\n        ]\n\n\nclass EndScene(Exception):\n    pass\n\n\nclass ThreeDScene(Scene):\n    samples = 4\n    default_frame_orientation = (-30, 70)\n    always_depth_test = True\n\n    def add(self, *mobjects: Mobject, set_depth_test: bool = True, perp_stroke: bool = True):\n        for mob in mobjects:\n            if set_depth_test and not mob.is_fixed_in_frame() and self.always_depth_test:\n                mob.apply_depth_test()\n            if isinstance(mob, VMobject) and mob.has_stroke() and perp_stroke:\n                mob.set_flat_stroke(False)\n        super().add(*mobjects)\n"
  },
  {
    "path": "manimlib/scene/scene_embed.py",
    "content": "from __future__ import annotations\n\nimport inspect\nimport pyperclip\nimport traceback\n\nfrom IPython.terminal import pt_inputhooks\nfrom IPython.terminal.embed import InteractiveShellEmbed\n\nfrom manimlib.animation.fading import VFadeInThenOut\nfrom manimlib.config import manim_config\nfrom manimlib.constants import RED\nfrom manimlib.mobject.mobject import Mobject\nfrom manimlib.mobject.frame import FullScreenRectangle\nfrom manimlib.module_loader import ModuleLoader\n\n\nfrom typing import TYPE_CHECKING\nif TYPE_CHECKING:\n    from manimlib.scene.scene import Scene\n\n\nclass InteractiveSceneEmbed:\n    def __init__(self, scene: Scene):\n        self.scene = scene\n        self.checkpoint_manager = CheckpointManager()\n\n        self.shell = self.get_ipython_shell_for_embedded_scene()\n        self.enable_gui()\n        self.ensure_frame_update_post_cell()\n        self.ensure_flash_on_error()\n        if manim_config.embed.autoreload:\n            self.auto_reload()\n\n    def launch(self):\n        self.shell()\n\n    def get_ipython_shell_for_embedded_scene(self) -> InteractiveShellEmbed:\n        \"\"\"\n        Create embedded IPython terminal configured to have access to\n        the local namespace of the caller\n        \"\"\"\n        # Triple back should take us to the context in a user's scene definition\n        # which is calling \"self.embed\"\n        caller_frame = inspect.currentframe().f_back.f_back.f_back\n\n        # Update the module's namespace to include local variables\n        module = ModuleLoader.get_module(caller_frame.f_globals[\"__file__\"])\n        module.__dict__.update(caller_frame.f_locals)\n        module.__dict__.update(self.get_shortcuts())\n        exception_mode = manim_config.embed.exception_mode\n\n        return InteractiveShellEmbed(\n            user_module=module,\n            display_banner=False,\n            xmode=exception_mode\n        )\n\n    def get_shortcuts(self):\n        \"\"\"\n        A few custom shortcuts useful to have in the interactive shell namespace\n        \"\"\"\n        scene = self.scene\n        return dict(\n            play=scene.play,\n            wait=scene.wait,\n            add=scene.add,\n            remove=scene.remove,\n            remove_all_except=scene.remove_all_except,\n            clear=scene.clear,\n            focus=scene.focus,\n            save_state=scene.save_state,\n            undo=scene.undo,\n            redo=scene.redo,\n            i2g=scene.i2g,\n            i2m=scene.i2m,\n            checkpoint_paste=self.checkpoint_paste,\n            clear_checkpoints=self.checkpoint_manager.clear_checkpoints,\n            reload=self.reload_scene  # Defined below\n        )\n\n    def enable_gui(self):\n        \"\"\"Enables gui interactions during the embed\"\"\"\n        def inputhook(context):\n            while not context.input_is_ready():\n                if not self.scene.is_window_closing():\n                    self.scene.update_frame(dt=0)\n            if self.scene.is_window_closing():\n                self.shell.ask_exit()\n\n        pt_inputhooks.register(\"manim\", inputhook)\n        self.shell.enable_gui(\"manim\")\n\n    def ensure_frame_update_post_cell(self):\n        \"\"\"Ensure the scene updates its frame after each ipython cell\"\"\"\n        def post_cell_func(*args, **kwargs):\n            if not self.scene.is_window_closing():\n                self.scene.update_frame(dt=0, force_draw=True)\n\n        self.shell.events.register(\"post_run_cell\", post_cell_func)\n\n    def ensure_flash_on_error(self):\n        \"\"\"Flash border, and potentially play sound, on exceptions\"\"\"\n        def custom_exc(shell, etype, evalue, tb, tb_offset=None):\n            # Show the error don't just swallow it\n            shell.showtraceback((etype, evalue, tb), tb_offset=tb_offset)\n            rect = FullScreenRectangle().set_stroke(RED, 30).set_fill(opacity=0)\n            rect.fix_in_frame()\n            self.scene.play(VFadeInThenOut(rect, run_time=0.5))\n\n        self.shell.set_custom_exc((Exception,), custom_exc)\n\n    def validate_syntax(self, file_path: str) -> bool:\n        \"\"\"\n        Validates the syntax of a Python file without executing it.\n        Returns True if syntax is valid, False otherwise.\n        Prints syntax errors to the console if found.\n        \"\"\"\n        try:\n            with open(file_path, 'r', encoding='utf-8') as f:\n                source_code = f.read()\n\n            # Use compile() to check for syntax errors without executing\n            compile(source_code, file_path, 'exec')\n            return True\n\n        except SyntaxError as e:\n            print(f\"\\nSyntax Error in {file_path}:\")\n            print(f\"  Line {e.lineno}: {e.text.strip() if e.text else ''}\")\n            print(f\"  {' ' * (e.offset - 1 if e.offset else 0)}^\")\n            print(f\"  {e.msg}\")\n            return False\n\n        except Exception as e:\n            print(f\"\\nError reading {file_path}: {e}\")\n            return False\n\n    def reload_scene(self, embed_line: int | None = None) -> None:\n        \"\"\"\n        Reloads the scene just like the `manimgl` command would do with the\n        same arguments that were provided for the initial startup. This allows\n        for quick iteration during scene development since we don't have to exit\n        the IPython kernel and re-run the `manimgl` command again. The GUI stays\n        open during the reload.\n\n        If `embed_line` is provided, the scene will be reloaded at that line\n        number. This corresponds to the `linemarker` param of the\n        `extract_scene.insert_embed_line_to_module()` method.\n\n        Before reload, the scene is cleared and the entire state is reset, such\n        that we can start from a clean slate. This is taken care of by the\n        run_scenes function in __main__.py, which will catch the error raised by the\n        `exit_raise` magic command that we invoke here.\n\n        Note that we cannot define a custom exception class for this error,\n        since the IPython kernel will swallow any exception. While we can catch\n        such an exception in our custom exception handler registered with the\n        `set_custom_exc` method, we cannot break out of the IPython shell by\n        this means.\n        \"\"\"\n        # Get the current file path for syntax validation\n        current_file = self.shell.user_module.__file__\n\n        # Validate syntax before attempting reload\n        if not self.validate_syntax(current_file):\n            print(\"[ERROR] Reload cancelled due to syntax errors. Fix the errors and try again.\")\n            return\n\n        # Update the global run configuration.\n        run_config = manim_config.run\n        run_config.is_reload = True\n        if embed_line:\n            run_config.embed_line = embed_line\n\n        print(\"Reloading...\")\n        self.shell.run_line_magic(\"exit_raise\", \"\")\n\n    def auto_reload(self):\n        \"\"\"Enables reload the shell's module before all calls\"\"\"\n        def pre_cell_func(*args, **kwargs):\n            new_mod = ModuleLoader.get_module(self.shell.user_module.__file__, is_during_reload=True)\n            self.shell.user_ns.update(vars(new_mod))\n\n        self.shell.events.register(\"pre_run_cell\", pre_cell_func)\n\n    def checkpoint_paste(\n        self,\n        skip: bool = False,\n        record: bool = False,\n        progress_bar: bool = True\n    ):\n        with self.scene.temp_config_change(skip, record, progress_bar):\n            self.checkpoint_manager.checkpoint_paste(self.shell, self.scene)\n\n\nclass CheckpointManager:\n    def __init__(self):\n        self.checkpoint_states: dict[str, list[tuple[Mobject, Mobject]]] = dict()\n\n    def checkpoint_paste(self, shell, scene):\n        \"\"\"\n        Used during interactive development to run (or re-run)\n        a block of scene code.\n\n        If the copied selection starts with a comment, this will\n        revert to the state of the scene the first time this function\n        was called on a block of code starting with that comment.\n        \"\"\"\n        code_string = pyperclip.paste()\n        checkpoint_key = self.get_leading_comment(code_string)\n        self.handle_checkpoint_key(scene, checkpoint_key)\n        shell.run_cell(code_string)\n\n    @staticmethod\n    def get_leading_comment(code_string: str) -> str:\n        leading_line = code_string.partition(\"\\n\")[0].lstrip()\n        if leading_line.startswith(\"#\"):\n            return leading_line\n        return \"\"\n\n    def handle_checkpoint_key(self, scene, key: str):\n        if not key:\n            return\n        elif key in self.checkpoint_states:\n            # Revert to checkpoint\n            scene.restore_state(self.checkpoint_states[key])\n\n            # Clear out any saved states that show up later\n            all_keys = list(self.checkpoint_states.keys())\n            index = all_keys.index(key)\n            for later_key in all_keys[index + 1:]:\n                self.checkpoint_states.pop(later_key)\n        else:\n            self.checkpoint_states[key] = scene.get_state()\n\n    def clear_checkpoints(self):\n        self.checkpoint_states = dict()\n"
  },
  {
    "path": "manimlib/scene/scene_file_writer.py",
    "content": "from __future__ import annotations\n\nimport os\nimport platform\nimport shutil\nimport subprocess as sp\nimport sys\n\nimport numpy as np\nfrom pydub import AudioSegment\nfrom tqdm.auto import tqdm as ProgressDisplay\nfrom pathlib import Path\n\nfrom manimlib.logger import log\nfrom manimlib.mobject.mobject import Mobject\nfrom manimlib.utils.file_ops import guarantee_existence\nfrom manimlib.utils.sounds import get_full_sound_file_path\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from PIL.Image import Image\n\n    from manimlib.camera.camera import Camera\n    from manimlib.scene.scene import Scene\n\n\nclass SceneFileWriter(object):\n    def __init__(\n        self,\n        scene: Scene,\n        write_to_movie: bool = False,\n        subdivide_output: bool = False,\n        png_mode: str = \"RGBA\",\n        save_last_frame: bool = False,\n        movie_file_extension: str = \".mp4\",\n        # Where should this be written\n        output_directory: str = \".\",\n        file_name: str | None = None,\n        open_file_upon_completion: bool = False,\n        show_file_location_upon_completion: bool = False,\n        quiet: bool = False,\n        total_frames: int = 0,\n        progress_description_len: int = 40,\n        # Name of the binary used for ffmpeg\n        ffmpeg_bin: str = \"ffmpeg\",\n        video_codec: str = \"libx264\",\n        pixel_format: str = \"yuv420p\",\n        saturation: float = 1.0,\n        gamma: float = 1.0,\n    ):\n        self.scene: Scene = scene\n        self.write_to_movie = write_to_movie\n        self.subdivide_output = subdivide_output\n        self.png_mode = png_mode\n        self.save_last_frame = save_last_frame\n        self.movie_file_extension = movie_file_extension\n        self.output_directory = output_directory\n        self.file_name = file_name\n        self.open_file_upon_completion = open_file_upon_completion\n        self.show_file_location_upon_completion = show_file_location_upon_completion\n        self.quiet = quiet\n        self.total_frames = total_frames\n        self.progress_description_len = progress_description_len\n        self.ffmpeg_bin = ffmpeg_bin\n        self.video_codec = video_codec\n        self.pixel_format = pixel_format\n        self.saturation = saturation\n        self.gamma = gamma\n\n        # State during file writing\n        self.writing_process: sp.Popen | None = None\n        self.progress_display: ProgressDisplay | None = None\n        self.ended_with_interrupt: bool = False\n\n        self.init_output_directories()\n        self.init_audio()\n\n    # Output directories and files\n    def init_output_directories(self) -> None:\n        if self.save_last_frame:\n            self.image_file_path = self.init_image_file_path()\n        if self.write_to_movie:\n            self.movie_file_path = self.init_movie_file_path()\n        if self.subdivide_output:\n            self.partial_movie_directory = self.init_partial_movie_directory()\n\n    def init_image_file_path(self) -> Path:\n        return self.get_output_file_rootname().with_suffix(\".png\")\n\n    def init_movie_file_path(self) -> Path:\n        return self.get_output_file_rootname().with_suffix(self.movie_file_extension)\n\n    def init_partial_movie_directory(self):\n        return guarantee_existence(self.get_output_file_rootname())\n\n    def get_output_file_rootname(self) -> Path:\n        return Path(\n            guarantee_existence(self.output_directory),\n            self.get_output_file_name()\n        )\n\n    def get_output_file_name(self) -> str:\n        if self.file_name:\n            return self.file_name\n        # Otherwise, use the name of the scene, potentially\n        # appending animation numbers\n        name = str(self.scene)\n        saan = self.scene.start_at_animation_number\n        eaan = self.scene.end_at_animation_number\n        if saan is not None:\n            name += f\"_{saan}\"\n        if eaan is not None:\n            name += f\"_{eaan}\"\n        return name\n\n    # Directory getters\n    def get_image_file_path(self) -> str:\n        return self.image_file_path\n\n    def get_next_partial_movie_path(self) -> str:\n        result = Path(self.partial_movie_directory, f\"{self.scene.num_plays:05}\")\n        return result.with_suffix(self.movie_file_extension)\n\n    def get_movie_file_path(self) -> str:\n        return self.movie_file_path\n\n    # Sound\n    def init_audio(self) -> None:\n        self.includes_sound: bool = False\n\n    def create_audio_segment(self) -> None:\n        self.audio_segment = AudioSegment.silent()\n\n    def add_audio_segment(\n        self,\n        new_segment: AudioSegment,\n        time: float | None = None,\n        gain_to_background: float | None = None\n    ) -> None:\n        if not self.includes_sound:\n            self.includes_sound = True\n            self.create_audio_segment()\n        segment = self.audio_segment\n        curr_end = segment.duration_seconds\n        if time is None:\n            time = curr_end\n        if time < 0:\n            raise Exception(\"Adding sound at timestamp < 0\")\n\n        new_end = time + new_segment.duration_seconds\n        diff = new_end - curr_end\n        if diff > 0:\n            segment = segment.append(\n                AudioSegment.silent(int(np.ceil(diff * 1000))),\n                crossfade=0,\n            )\n        self.audio_segment = segment.overlay(\n            new_segment,\n            position=int(1000 * time),\n            gain_during_overlay=gain_to_background,\n        )\n\n    def add_sound(\n        self,\n        sound_file: str,\n        time: float | None = None,\n        gain: float | None = None,\n        gain_to_background: float | None = None\n    ) -> None:\n        file_path = get_full_sound_file_path(sound_file)\n        new_segment = AudioSegment.from_file(file_path)\n        if gain:\n            new_segment = new_segment.apply_gain(gain)\n        self.add_audio_segment(new_segment, time, gain_to_background)\n\n    # Writers\n    def begin(self) -> None:\n        if not self.subdivide_output and self.write_to_movie:\n            self.open_movie_pipe(self.get_movie_file_path())\n\n    def begin_animation(self) -> None:\n        if self.subdivide_output and self.write_to_movie:\n            self.open_movie_pipe(self.get_next_partial_movie_path())\n\n    def end_animation(self) -> None:\n        if self.subdivide_output and self.write_to_movie:\n            self.close_movie_pipe()\n\n    def finish(self) -> None:\n        if not self.subdivide_output and self.write_to_movie:\n            self.close_movie_pipe()\n            if self.includes_sound:\n                self.add_sound_to_video()\n            self.print_file_ready_message(self.get_movie_file_path())\n        if self.save_last_frame:\n            self.scene.update_frame(force_draw=True)\n            self.save_final_image(self.scene.get_image())\n        if self.should_open_file():\n            self.open_file()\n\n    def open_movie_pipe(self, file_path: str) -> None:\n        stem, ext = os.path.splitext(file_path)\n        self.final_file_path = file_path\n        self.temp_file_path = stem + \"_temp\" + ext\n\n        fps = self.scene.camera.fps\n        width, height = self.scene.camera.get_pixel_shape()\n\n        vf_arg = 'vflip'\n        vf_arg += f',eq=saturation={self.saturation}:gamma={self.gamma}'\n\n        command = [\n            self.ffmpeg_bin,\n            '-y',  # overwrite output file if it exists\n            '-f', 'rawvideo',\n            '-s', f'{width}x{height}',  # size of one frame\n            '-pix_fmt', 'rgba',\n            '-r', str(fps),  # frames per second\n            '-i', '-',  # The input comes from a pipe\n            '-vf', vf_arg,\n            '-an',  # Tells ffmpeg not to expect any audio\n            '-loglevel', 'error',\n        ]\n        if self.video_codec:\n            command += ['-vcodec', self.video_codec]\n        if self.pixel_format:\n            command += ['-pix_fmt', self.pixel_format]\n        command += [self.temp_file_path]\n        self.writing_process = sp.Popen(command, stdin=sp.PIPE)\n\n        if not self.quiet:\n            self.progress_display = ProgressDisplay(\n                range(self.total_frames),\n                leave=False,\n                ascii=True if platform.system() == 'Windows' else None,\n                dynamic_ncols=True,\n            )\n            self.set_progress_display_description()\n\n    def use_fast_encoding(self):\n        self.video_codec = \"libx264rgb\"\n        self.pixel_format = \"rgb32\"\n\n    def get_insert_file_path(self, index: int) -> Path:\n        movie_path = Path(self.get_movie_file_path())\n        scene_name = movie_path.stem\n        insert_dir = Path(movie_path.parent, \"inserts\")\n        guarantee_existence(insert_dir)\n        return Path(insert_dir, f\"{scene_name}_{index}\").with_suffix(self.movie_file_extension)\n\n    def begin_insert(self):\n        # Begin writing process\n        self.write_to_movie = True\n        self.init_output_directories()\n        index = 0\n        while (insert_path := self.get_insert_file_path(index)).exists():\n            index += 1\n        self.inserted_file_path = insert_path\n        self.open_movie_pipe(self.inserted_file_path)\n\n    def end_insert(self):\n        self.close_movie_pipe()\n        self.write_to_movie = False\n        self.print_file_ready_message(self.inserted_file_path)\n\n    def has_progress_display(self):\n        return self.progress_display is not None\n\n    def set_progress_display_description(self, file: str = \"\", sub_desc: str = \"\") -> None:\n        if self.progress_display is None:\n            return\n\n        desc_len = self.progress_description_len\n        if not file:\n            file = os.path.split(self.get_movie_file_path())[1]\n        full_desc = f\"{file} {sub_desc}\"\n        if len(full_desc) > desc_len:\n            full_desc = full_desc[:desc_len - 3] + \"...\"\n        else:\n            full_desc += \" \" * (desc_len - len(full_desc))\n        self.progress_display.set_description(full_desc)\n\n    def write_frame(self, camera: Camera) -> None:\n        if self.write_to_movie:\n            raw_bytes = camera.get_raw_fbo_data()\n            self.writing_process.stdin.write(raw_bytes)\n            if self.progress_display is not None:\n                self.progress_display.update()\n\n    def close_movie_pipe(self) -> None:\n        self.writing_process.stdin.close()\n        self.writing_process.wait()\n        self.writing_process.terminate()\n        if self.progress_display is not None:\n            self.progress_display.close()\n\n        if not self.ended_with_interrupt:\n            shutil.move(self.temp_file_path, self.final_file_path)\n        else:\n            self.movie_file_path = self.temp_file_path\n\n    def add_sound_to_video(self) -> None:\n        movie_file_path = self.get_movie_file_path()\n        stem, ext = os.path.splitext(movie_file_path)\n        sound_file_path = stem + \".wav\"\n        # Makes sure sound file length will match video file\n        self.add_audio_segment(AudioSegment.silent(0))\n        self.audio_segment.export(\n            sound_file_path,\n            bitrate='312k',\n        )\n        temp_file_path = stem + \"_temp\" + ext\n        commands = [\n            self.ffmpeg_bin,\n            \"-i\", movie_file_path,\n            \"-i\", sound_file_path,\n            '-y',  # overwrite output file if it exists\n            \"-c:v\", \"copy\",\n            \"-c:a\", \"aac\",\n            \"-b:a\", \"320k\",\n            # select video stream from first file\n            \"-map\", \"0:v:0\",\n            # select audio stream from second file\n            \"-map\", \"1:a:0\",\n            '-loglevel', 'error',\n            # \"-shortest\",\n            temp_file_path,\n        ]\n        sp.call(commands)\n        shutil.move(temp_file_path, movie_file_path)\n        os.remove(sound_file_path)\n\n    def save_final_image(self, image: Image) -> None:\n        file_path = self.get_image_file_path()\n        image.save(file_path)\n        self.print_file_ready_message(file_path)\n\n    def print_file_ready_message(self, file_path: str) -> None:\n        if not self.quiet:\n            log.info(f\"File ready at {file_path}\")\n\n    def should_open_file(self) -> bool:\n        return any([\n            self.show_file_location_upon_completion,\n            self.open_file_upon_completion,\n        ])\n\n    def open_file(self) -> None:\n        if self.quiet:\n            curr_stdout = sys.stdout\n            sys.stdout = open(os.devnull, \"w\")\n\n        current_os = platform.system()\n        file_paths = []\n\n        if self.save_last_frame:\n            file_paths.append(self.get_image_file_path())\n        if self.write_to_movie:\n            file_paths.append(self.get_movie_file_path())\n\n        for file_path in file_paths:\n            if current_os == \"Windows\":\n                os.startfile(file_path)\n            else:\n                commands = []\n                if current_os == \"Linux\":\n                    commands.append(\"xdg-open\")\n                elif current_os.startswith(\"CYGWIN\"):\n                    commands.append(\"cygstart\")\n                else:  # Assume macOS\n                    commands.append(\"open\")\n\n                if self.show_file_location_upon_completion:\n                    commands.append(\"-R\")\n\n                commands.append(file_path)\n\n                FNULL = open(os.devnull, 'w')\n                sp.call(commands, stdout=FNULL, stderr=sp.STDOUT)\n                FNULL.close()\n\n        if self.quiet:\n            sys.stdout.close()\n            sys.stdout = curr_stdout\n"
  },
  {
    "path": "manimlib/shader_wrapper.py",
    "content": "from __future__ import annotations\n\nimport copy\nimport os\nimport re\n\nimport OpenGL.GL as gl\nimport moderngl\nimport numpy as np\nfrom functools import lru_cache\n\nfrom manimlib.config import parse_cli\nfrom manimlib.config import manim_config\nfrom manimlib.utils.shaders import get_shader_code_from_file\nfrom manimlib.utils.shaders import get_shader_program\nfrom manimlib.utils.shaders import image_path_to_texture\nfrom manimlib.utils.shaders import set_program_uniform\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Optional, Tuple, Iterable\n    from manimlib.typing import UniformDict\n    from moderngl.vertex_array import VertexArray\n    from moderngl.framebuffer import Framebuffer\n\n# Mobjects that should be rendered with\n# the same shader will be organized and\n# clumped together based on keeping track\n# of a dict holding all the relevant information\n# to that shader\n\n\nclass ShaderWrapper(object):\n    def __init__(\n        self,\n        ctx: moderngl.context.Context,\n        vert_data: np.ndarray,\n        shader_folder: Optional[str] = None,\n        mobject_uniforms: Optional[UniformDict] = None,  # A dictionary mapping names of uniform variables\n        texture_paths: Optional[dict[str, str]] = None,  # A dictionary mapping names to filepaths for textures.\n        depth_test: bool = False,\n        render_primitive: int = moderngl.TRIANGLE_STRIP,\n        code_replacements: dict[str, str] = dict(),\n    ):\n        self.ctx = ctx\n        self.vert_data = vert_data\n        self.vert_attributes = vert_data.dtype.names\n        self.shader_folder = shader_folder\n        self.depth_test = depth_test\n        self.render_primitive = render_primitive\n        self.texture_paths = texture_paths or dict()\n\n        self.program_uniform_mirror: UniformDict = dict()\n        self.bind_to_mobject_uniforms(mobject_uniforms or dict())\n\n        self.init_program_code()\n        for old, new in code_replacements.items():\n            self.replace_code(old, new)\n        self.init_program()\n        self.init_textures()\n        self.init_vertex_objects()\n        self.refresh_id()\n\n    def __deepcopy__(self, memo):\n        # Don't allow deepcopies, e.g. if the mobject with this ShaderWrapper as an\n        # attribute gets copies. Returning None means the parent object with this ShaderWrapper\n        # as an attribute should smoothly handle this case.\n        return None\n\n    def init_program_code(self) -> None:\n        def get_code(name: str) -> str | None:\n            return get_shader_code_from_file(\n                os.path.join(self.shader_folder, f\"{name}.glsl\")\n            )\n\n        self.program_code: dict[str, str | None] = {\n            \"vertex_shader\": get_code(\"vert\"),\n            \"geometry_shader\": get_code(\"geom\"),\n            \"fragment_shader\": get_code(\"frag\"),\n        }\n\n    def init_program(self):\n        if not self.shader_folder:\n            self.program = None\n            self.vert_format = None\n            self.programs = []\n            return\n        self.program = get_shader_program(self.ctx, **self.program_code)\n        self.vert_format = moderngl.detect_format(self.program, self.vert_attributes)\n        self.programs = [self.program]\n\n    def init_textures(self):\n        self.texture_names_to_ids = dict()\n        self.textures = []\n        for name, path in self.texture_paths.items():\n            self.add_texture(name, image_path_to_texture(path, self.ctx))\n\n    def init_vertex_objects(self):\n        self.vbo = None\n        self.vaos = []\n\n    def add_texture(self, name: str, texture: moderngl.Texture):\n        max_units = self.ctx.info['GL_MAX_TEXTURE_IMAGE_UNITS']\n        if len(self.textures) >= max_units:\n            raise ValueError(f\"Unable to use more than {max_units} textures for a program\")\n        # The position in the list determines its id\n        self.texture_names_to_ids[name] = len(self.textures)\n        self.textures.append(texture)\n\n    def bind_to_mobject_uniforms(self, mobject_uniforms: UniformDict):\n        self.mobject_uniforms = mobject_uniforms\n\n    def get_id(self) -> int:\n        return self.id\n\n    def refresh_id(self) -> None:\n        self.id = hash(\"\".join(map(str, [\n            \"\".join(map(str, self.program_code.values())),\n            self.mobject_uniforms,\n            self.depth_test,\n            self.render_primitive,\n            self.texture_paths,\n        ])))\n\n    def replace_code(self, old: str, new: str) -> None:\n        code_map = self.program_code\n        for name in code_map:\n            if code_map[name] is None:\n                continue\n            code_map[name] = re.sub(old, new, code_map[name])\n        self.init_program()\n        self.refresh_id()\n\n    # Changing context\n    def num_clip_planes(self):\n        count = 0\n        for n in range(4):\n            key = f\"clip_plane{n}\"\n            if key in self.mobject_uniforms and any(self.mobject_uniforms[key]):\n                count = n + 1\n        return count\n\n    def set_ctx_depth_test(self, enable: bool = True) -> None:\n        if enable:\n            self.ctx.enable(moderngl.DEPTH_TEST)\n        else:\n            self.ctx.disable(moderngl.DEPTH_TEST)\n\n    def set_ctx_clip_plane(self, num_planes: int = 0) -> None:\n        clip_distances = [\n            gl.GL_CLIP_DISTANCE0,\n            gl.GL_CLIP_DISTANCE1,\n            gl.GL_CLIP_DISTANCE2,\n            gl.GL_CLIP_DISTANCE3,\n        ]\n        for n, clip_dist in enumerate(clip_distances):\n            if n < num_planes:\n                gl.glEnable(clip_dist)\n            else:\n                gl.glDisable(clip_dist)\n\n    # Adding data\n\n    def read_in(self, data_list: Iterable[np.ndarray]):\n        total_len = sum(map(len, data_list))\n        if total_len == 0:\n            if self.vbo is not None:\n                self.vbo.clear()\n            return\n\n        # If possible, read concatenated data into existing list\n        if len(self.vert_data) != total_len:\n            self.vert_data = np.concatenate(data_list)\n        else:\n            np.concatenate(data_list, out=self.vert_data)\n\n        # Either create new vbo, or read data into it\n        total_size = self.vert_data.itemsize * total_len\n        if self.vbo is not None and self.vbo.size != total_size:\n            self.release()  # This sets vbo to be None\n        if self.vbo is None:\n            self.vbo = self.ctx.buffer(self.vert_data)\n            self.generate_vaos()\n        else:\n            self.vbo.write(self.vert_data)\n\n    def generate_vaos(self):\n        # Vertex array object\n        self.vaos = [\n            self.ctx.vertex_array(\n                program=program,\n                content=[(self.vbo, self.vert_format, *self.vert_attributes)],\n                mode=self.render_primitive,\n            )\n            for program in self.programs\n        ]\n\n    # Related to data and rendering\n    def pre_render(self):\n        self.set_ctx_depth_test(self.depth_test)\n        self.set_ctx_clip_plane(self.num_clip_planes())\n        for tid, texture in enumerate(self.textures):\n            texture.use(tid)\n\n    def render(self):\n        for vao in self.vaos:\n            vao.render()\n\n    def update_program_uniforms(self, camera_uniforms: UniformDict):\n        for program in self.programs:\n            if program is None:\n                continue\n            for uniforms in [self.mobject_uniforms, camera_uniforms, self.texture_names_to_ids]:\n                for name, value in uniforms.items():\n                    set_program_uniform(program, name, value)\n\n    def release(self):\n        for obj in (self.vbo, *self.vaos):\n            if obj is not None:\n                obj.release()\n        self.init_vertex_objects()\n\n    def release_textures(self):\n        for texture in self.textures:\n            texture.release()\n            del texture\n        self.textures = []\n        self.texture_names_to_ids = dict()\n\n\nclass VShaderWrapper(ShaderWrapper):\n    def __init__(\n        self,\n        ctx: moderngl.context.Context,\n        vert_data: np.ndarray,\n        shader_folder: Optional[str] = None,\n        mobject_uniforms: Optional[UniformDict] = None,  # A dictionary mapping names of uniform variables\n        texture_paths: Optional[dict[str, str]] = None,  # A dictionary mapping names to filepaths for textures.\n        depth_test: bool = False,\n        render_primitive: int = moderngl.TRIANGLES,\n        code_replacements: dict[str, str] = dict(),\n        stroke_behind: bool = False,\n    ):\n        self.stroke_behind = stroke_behind\n        super().__init__(\n            ctx=ctx,\n            vert_data=vert_data,\n            shader_folder=shader_folder,\n            mobject_uniforms=mobject_uniforms,\n            texture_paths=texture_paths,\n            depth_test=depth_test,\n            render_primitive=render_primitive,\n            code_replacements=code_replacements,\n        )\n        self.fill_canvas = VShaderWrapper.get_fill_canvas(self.ctx)\n        self.add_texture('Texture', self.fill_canvas[0].color_attachments[0])\n        self.add_texture('DepthTexture', self.fill_canvas[2].color_attachments[0])\n\n    def init_program_code(self) -> None:\n        self.program_code = {\n            f\"{vtype}_{name}\": get_shader_code_from_file(\n                os.path.join(\"quadratic_bezier\", f\"{vtype}\", f\"{name}.glsl\")\n            )\n            for vtype in [\"stroke\", \"fill\", \"depth\"]\n            for name in [\"vert\", \"geom\", \"frag\"]\n        }\n\n    def init_program(self):\n        self.stroke_program = get_shader_program(\n            self.ctx,\n            vertex_shader=self.program_code[\"stroke_vert\"],\n            geometry_shader=self.program_code[\"stroke_geom\"],\n            fragment_shader=self.program_code[\"stroke_frag\"],\n        )\n        self.fill_program = get_shader_program(\n            self.ctx,\n            vertex_shader=self.program_code[\"fill_vert\"],\n            geometry_shader=self.program_code[\"fill_geom\"],\n            fragment_shader=self.program_code[\"fill_frag\"],\n        )\n        self.fill_border_program = get_shader_program(\n            self.ctx,\n            vertex_shader=self.program_code[\"stroke_vert\"],\n            geometry_shader=self.program_code[\"stroke_geom\"],\n            fragment_shader=self.program_code[\"stroke_frag\"].replace(\n                \"// MODIFY FRAG COLOR\",\n                \"frag_color.a *= 0.95; frag_color.rgb *= frag_color.a;\",\n            )\n        )\n        self.fill_depth_program = get_shader_program(\n            self.ctx,\n            vertex_shader=self.program_code[\"depth_vert\"],\n            geometry_shader=self.program_code[\"depth_geom\"],\n            fragment_shader=self.program_code[\"depth_frag\"],\n        )\n        self.programs = [self.stroke_program, self.fill_program, self.fill_border_program, self.fill_depth_program]\n\n        # Full vert format looks like this (total of 4x23 = 92 bytes):\n        # point 3\n        # stroke_rgba 4\n        # stroke_width 1\n        # joint_angle 1\n        # fill_rgba 4\n        # base_normal 3\n        # fill_border_width 1\n        self.stroke_vert_format = '3f 4f 1f 1f 16x 3f 4x'\n        self.stroke_vert_attributes = ['point', 'stroke_rgba', 'stroke_width', 'joint_angle', 'unit_normal']\n\n        self.fill_vert_format = '3f 24x 4f 3f 4x'\n        self.fill_vert_attributes = ['point', 'fill_rgba', 'base_normal']\n\n        self.fill_border_vert_format = '3f 20x 1f 4f 3f 1f'\n        self.fill_border_vert_attributes = ['point', 'joint_angle', 'stroke_rgba', 'unit_normal', 'stroke_width']\n\n        self.fill_depth_vert_format = '3f 40x 3f 4x'\n        self.fill_depth_vert_attributes = ['point', 'base_normal']\n\n    def init_vertex_objects(self):\n        self.vbo = None\n        self.stroke_vao = None\n        self.fill_vao = None\n        self.fill_border_vao = None\n        self.vaos = []\n\n    def generate_vaos(self):\n        self.stroke_vao = self.ctx.vertex_array(\n            program=self.stroke_program,\n            content=[(self.vbo, self.stroke_vert_format, *self.stroke_vert_attributes)],\n            mode=self.render_primitive,\n        )\n        self.fill_vao = self.ctx.vertex_array(\n            program=self.fill_program,\n            content=[(self.vbo, self.fill_vert_format, *self.fill_vert_attributes)],\n            mode=self.render_primitive,\n        )\n        self.fill_border_vao = self.ctx.vertex_array(\n            program=self.fill_border_program,\n            content=[(self.vbo, self.fill_border_vert_format, *self.fill_border_vert_attributes)],\n            mode=self.render_primitive,\n        )\n        self.fill_depth_vao = self.ctx.vertex_array(\n            program=self.fill_depth_program,\n            content=[(self.vbo, self.fill_depth_vert_format, *self.fill_depth_vert_attributes)],\n            mode=self.render_primitive,\n        )\n        self.vaos = [self.stroke_vao, self.fill_vao, self.fill_border_vao, self.fill_depth_vao]\n\n    def set_backstroke(self, value: bool = True):\n        self.stroke_behind = value\n\n    def refresh_id(self):\n        super().refresh_id()\n        self.id = hash(str(self.id) + str(self.stroke_behind))\n\n    # Rendering\n    def render_stroke(self):\n        if self.stroke_vao is None:\n            return\n        self.stroke_vao.render()\n\n    def render_fill(self):\n        if self.fill_vao is None:\n            return\n\n        original_fbo = self.ctx.fbo\n        fill_tx_fbo, fill_tx_vao, depth_tx_fbo = self.fill_canvas\n\n        # Render to a separate texture, due to strange alpha compositing\n        # for the blended winding calculation\n        fill_tx_fbo.clear()\n        fill_tx_fbo.use()\n\n        # Be sure not to apply depth test while rendering fill\n        # but set it back to where it was after\n        apply_depth_test = bool(gl.glGetBooleanv(gl.GL_DEPTH_TEST))\n        self.ctx.disable(moderngl.DEPTH_TEST)\n\n        # With this blend function, the effect of blending alpha a with\n        # -a / (1 - a) cancels out, so we can cancel positively and negatively\n        # oriented triangles\n        gl.glBlendFuncSeparate(\n            gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA,\n            gl.GL_ONE_MINUS_DST_ALPHA, gl.GL_ONE\n        )\n        self.fill_vao.render()\n\n        if apply_depth_test:\n            self.ctx.enable(moderngl.DEPTH_TEST)\n            depth_tx_fbo.clear(1.0)\n            depth_tx_fbo.use()\n            gl.glBlendFunc(gl.GL_ONE, gl.GL_ONE)\n            gl.glBlendEquation(gl.GL_MIN)\n            self.fill_depth_vao.render()\n\n        # Now add border, just taking the max alpha\n        gl.glBlendFunc(gl.GL_ONE, gl.GL_ONE)\n        gl.glBlendEquation(gl.GL_MAX)\n        self.fill_border_vao.render()\n\n        # Take the texture we were just drawing to, and render it to\n        # the main scene. Account for how alphas have been premultiplied\n        original_fbo.use()\n        gl.glBlendFunc(gl.GL_ONE, gl.GL_ONE_MINUS_SRC_ALPHA)\n        gl.glBlendEquation(gl.GL_FUNC_ADD)\n        fill_tx_vao.render()\n\n        # Return to original blending state\n        gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)\n\n    # Static method returning one shared value across all VShaderWrappers\n    @lru_cache\n    @staticmethod\n    def get_fill_canvas(ctx: moderngl.Context) -> Tuple[Framebuffer, VertexArray, Framebuffer]:\n        \"\"\"\n        Because VMobjects with fill are rendered in a funny way, using\n        alpha blending to effectively compute the winding number around\n        each pixel, they need to be rendered to a separate texture, which\n        is then composited onto the ordinary frame buffer.\n\n        This returns a texture, loaded into a frame buffer, and a vao\n        which can display that texture as a simple quad onto a screen,\n        along with the rgb value which is meant to be discarded.\n        \"\"\"\n        size = manim_config.camera.resolution\n        double_size = (2 * size[0], 2 * size[1])\n\n        # Important to make sure dtype is floating point (not fixed point)\n        # so that alpha values can be negative and are not clipped\n        fill_texture = ctx.texture(size=double_size, components=4, dtype='f2')\n        # Use another one to keep track of depth\n        depth_texture = ctx.texture(size=size, components=1, dtype='f4')\n\n        fill_texture_fbo = ctx.framebuffer(fill_texture)\n        depth_texture_fbo = ctx.framebuffer(depth_texture)\n\n        simple_vert = '''\n            #version 330\n\n            in vec2 texcoord;\n            out vec2 uv;\n\n            void main() {\n                gl_Position = vec4((2.0 * texcoord - 1.0), 0.0, 1.0);\n                uv = texcoord;\n            }\n        '''\n        alpha_adjust_frag = '''\n            #version 330\n\n            uniform sampler2D Texture;\n            uniform sampler2D DepthTexture;\n\n            in vec2 uv;\n            out vec4 color;\n\n            void main() {\n                color = texture(Texture, uv);\n                if(color.a == 0) discard;\n\n                if(color.a < 0){\n                    color.a = -color.a / (1.0 - color.a);\n                    color.rgb *= (color.a - 1);\n                }\n\n                // Counteract scaling in fill frag\n                color *= 1.06;\n\n                gl_FragDepth = texture(DepthTexture, uv)[0];\n            }\n        '''\n        fill_program = ctx.program(\n            vertex_shader=simple_vert,\n            fragment_shader=alpha_adjust_frag,\n        )\n\n        verts = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])\n        simple_vbo = ctx.buffer(verts.astype('f4').tobytes())\n        fill_texture_vao = ctx.simple_vertex_array(\n            fill_program, simple_vbo, 'texcoord',\n            mode=moderngl.TRIANGLE_STRIP\n        )\n\n        return (fill_texture_fbo, fill_texture_vao, depth_texture_fbo)\n\n    def render(self):\n        if self.stroke_behind:\n            self.render_stroke()\n            self.render_fill()\n        else:\n            self.render_fill()\n            self.render_stroke()\n"
  },
  {
    "path": "manimlib/shaders/image/frag.glsl",
    "content": "#version 330\n\nuniform sampler2D Texture;\n\nin vec2 v_im_coords;\nin float v_opacity;\n\nout vec4 frag_color;\n\nvoid main() {\n    frag_color = texture(Texture, v_im_coords);\n    frag_color.a *= v_opacity;\n}"
  },
  {
    "path": "manimlib/shaders/image/vert.glsl",
    "content": "#version 330\n\nuniform sampler2D Texture;\n\nin vec3 point;\nin vec2 im_coords;\nin float opacity;\n\nout vec2 v_im_coords;\nout float v_opacity;\n\n// Analog of import for manim only\n#INSERT emit_gl_Position.glsl\n\nvoid main(){\n    v_im_coords = im_coords;\n    v_opacity = opacity;\n    emit_gl_Position(point);\n}"
  },
  {
    "path": "manimlib/shaders/inserts/NOTE.md",
    "content": "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 \n\n#INSERT <file_name>\n\nwith the code from one of the files in this folder.\n\nThe functions in this file may include declarations of uniforms, so one should not re-declare those in the surrounding context.\n"
  },
  {
    "path": "manimlib/shaders/inserts/complex_functions.glsl",
    "content": "vec2 complex_mult(vec2 z, vec2 w){\n    return vec2(z.x * w.x - z.y * w.y, z.x * w.y + z.y * w.x);\n}\n\nvec2 complex_div(vec2 z, vec2 w){\n    return complex_mult(z, vec2(w.x, -w.y)) / (w.x * w.x + w.y * w.y);\n}\n\nvec2 complex_pow(vec2 z, int n){\n    vec2 result = vec2(1.0, 0.0);\n    for(int i = 0; i < n; i++){\n        result = complex_mult(result, z);\n    }\n    return result;\n}"
  },
  {
    "path": "manimlib/shaders/inserts/emit_gl_Position.glsl",
    "content": "uniform float is_fixed_in_frame;\nuniform mat4 view;\nuniform float focal_distance;\nuniform vec3 frame_rescale_factors;\nuniform vec4 clip_plane0;\nuniform vec4 clip_plane1;\nuniform vec4 clip_plane2;\nuniform vec4 clip_plane3;\n\nvoid emit_gl_Position(vec3 point){\n    vec4 result = vec4(point, 1.0);\n    // This allows for smooth transitions between objects fixed and unfixed from frame\n    result = mix(view * result, result, is_fixed_in_frame);\n    // Essentially a projection matrix\n    result.xyz *= frame_rescale_factors;\n    result.w = 1.0 - result.z;\n    // Flip and scale to prevent premature clipping\n    result.z *= -0.1;\n    gl_Position = result;\n    \n    // Set clip planes\n    if(clip_plane0.xyz != vec3(0.0, 0.0, 0.0)){\n        gl_ClipDistance[0] = dot(vec4(point, 1.0), clip_plane0);\n    }\n    if(clip_plane1.xyz != vec3(0.0, 0.0, 0.0)){\n        gl_ClipDistance[1] = dot(vec4(point, 1.0), clip_plane1);\n    }\n    if(clip_plane2.xyz != vec3(0.0, 0.0, 0.0)){\n        gl_ClipDistance[2] = dot(vec4(point, 1.0), clip_plane2);\n    }\n    if(clip_plane3.xyz != vec3(0.0, 0.0, 0.0)){\n        gl_ClipDistance[3] = dot(vec4(point, 1.0), clip_plane3);\n    }\n}\n"
  },
  {
    "path": "manimlib/shaders/inserts/finalize_color.glsl",
    "content": "uniform vec3 light_position;\nuniform vec3 camera_position;\nuniform vec3 shading;\n\nvec3 float_to_color(float value, float min_val, float max_val, vec3[9] colormap_data){\n    float alpha = clamp((value - min_val) / (max_val - min_val), 0.0, 1.0);\n    int disc_alpha = min(int(alpha * 8), 7);\n    return mix(\n        colormap_data[disc_alpha],\n        colormap_data[disc_alpha + 1],\n        8.0 * alpha - disc_alpha\n    );\n}\n\n\nvec4 add_light(vec4 color, vec3 point, vec3 unit_normal){\n    if(shading == vec3(0.0)) return color;\n\n    float reflectiveness = shading.x;\n    float gloss = shading.y;\n    float shadow = shading.z;\n\n    vec4 result = color;\n    vec3 to_camera = normalize(camera_position - point);\n    vec3 to_light = normalize(light_position - point);\n\n    float light_to_normal = dot(to_light, unit_normal);\n    // When unit normal points towards light, brighten\n    float bright_factor = max(light_to_normal, 0) * reflectiveness;\n    // For glossy surface, add extra shine if light beam goes towards camera\n    vec3 light_reflection = reflect(-to_light, unit_normal);\n    float light_to_cam = dot(light_reflection, to_camera);\n    float shine = gloss * exp(-3 * pow(1 - light_to_cam, 2));\n    bright_factor += shine;\n\n    result.rgb = mix(result.rgb, vec3(1.0), bright_factor);\n    if (light_to_normal < 0){\n        // Darken\n        result.rgb = mix(\n            result.rgb,\n            vec3(0.0),\n            max(-light_to_normal, 0) * shadow\n        );\n    }\n    return result;\n}\n\nvec4 finalize_color(vec4 color, vec3 point, vec3 unit_normal){\n    ///// INSERT COLOR FUNCTION HERE /////\n    // The line above may be replaced by arbitrary code snippets, as per\n    // the method Mobject.set_color_by_code\n    return add_light(color, point, unit_normal);\n}"
  },
  {
    "path": "manimlib/shaders/inserts/get_unit_normal.glsl",
    "content": "vec3 get_unit_normal(vec3 p0, vec3 p1, vec3 p2){\n    float tol = 1e-6;\n    vec3 v1 = normalize(p1 - p0);\n    vec3 v2 = normalize(p2 - p0);\n    vec3 cp = cross(v1, v2);\n    float cp_norm = length(cp);\n\n    if(cp_norm > tol) return cp / cp_norm;\n\n    // Otherwise, three pionts form a line, so find\n    // a normal vector to that line in the plane shared\n    // with the z-axis\n    vec3 comb = v1 + v2;\n    cp = cross(cross(comb, vec3(0.0, 0.0, 1.0)), comb);\n    cp_norm = length(cp);\n    if(cp_norm > tol) return cp / cp_norm;\n\n    // Otherwise, the points line up with the z-axis.\n    return vec3(0.0, -1.0, 0.0);\n}"
  },
  {
    "path": "manimlib/shaders/inserts/get_xyz_to_uv.glsl",
    "content": "vec2 xs_on_clean_parabola(vec3 b0, vec3 b1, vec3 b2){\n    /*\n    Given three control points for a quadratic bezier,\n    this returns the two values (x0, x2) such that the\n    section of the parabola y = x^2 between those values\n    is isometric to the given quadratic bezier.\n\n    Adapated from https://raphlinus.github.io/graphics/curves/2019/12/23/flatten-quadbez.html\n    */\n    vec3 dd = 2 * b1 - b0 - b2;\n\n    float u0 = dot(b1 - b0, dd);\n    float u2 = dot(b2 - b1, dd);\n    float cp = length(cross(b2 - b0, dd));\n\n    return vec2(u0 / cp, u2 / cp);\n}\n\n\nmat4 map_triangles(vec3 src0, vec3 src1, vec3 src2, vec3 dst0, vec3 dst1, vec3 dst2){\n    /*\n    Return an affine transform which maps the triangle (src0, src1, src2)\n    onto the triangle (dst0, dst1, dst2)\n    */\n    mat4 src_mat = mat4(\n        src0, 1.0,\n        src1, 1.0,\n        src2, 1.0,\n        vec4(1.0)\n    );\n    mat4 dst_mat = mat4(\n        dst0, 1.0,\n        dst1, 1.0,\n        dst2, 1.0,\n        vec4(1.0)\n    );\n    return dst_mat * inverse(src_mat);\n}\n\n\nmat4 rotation(vec3 axis, float cos_angle){\n    float c = cos_angle;\n    float s = sqrt(1 - c * c);  // Sine of the angle\n    float oc = 1.0 - c;\n    float ax = axis.x;\n    float ay = axis.y;\n    float az = axis.z;\n\n    return mat4(\n        oc * ax * ax + c,      oc * ax * ay + az * s, oc * az * ax - ay * s, 0.0,\n        oc * ax * ay - az * s, oc * ay * ay + c,      oc * ay * az + ax * s, 0.0,\n        oc * az * ax + ay * s, oc * ay * az - ax * s, oc * az * az + c,      0.0,\n        0.0, 0.0, 0.0, 1.0\n    );\n}\n\n\nmat4 map_onto_x_axis(vec3 src0, vec3 src1){\n    mat4 shift = mat4(1.0);\n    shift[3].xyz = -src0;\n\n    // Find rotation matrix between unit vectors in each direction    \n    vec3 vect = normalize(src1 - src0);\n    // No rotation needed\n    if(vect.x > 1 - 1e-6) return shift;\n\n    // Equivalent to cross(vect, vec3(1, 0, 0))\n    vec3 axis = normalize(vec3(0.0, vect.z, -vect.y));\n    mat4 rotate = rotation(axis, vect.x);\n    return rotate * shift;\n}\n\n\nmat4 get_xyz_to_uv(\n    vec3 b0, vec3 b1, vec3 b2,\n    float threshold,\n    out bool exceeds_threshold\n){\n    /*\n    Populates the matrix `result` with an affine transformation which maps a set of\n    quadratic bezier controls points into a new coordinate system such that the bezier\n    curve coincides with y = x^2.\n\n    If the x-range under this part of the curve exceeds `threshold`, this returns false\n    and populates result a matrix mapping b0 and b2 onto the x-axis\n    */\n    vec2 xs = xs_on_clean_parabola(b0, b1, b2);\n    float x0 = xs[0];\n    float x1 = 0.5 * (xs[0] + xs[1]);\n    float x2 = xs[1];\n    // Portions of the parabola y = x^2 where abs(x) exceeds\n    // this value are treated as straight lines.\n    exceeds_threshold = (min(x0, x2) > threshold || max(x0, x2) < -threshold);\n    if(exceeds_threshold){\n        return map_onto_x_axis(b0, b2);\n    }\n    // This triangle on the xy plane should be isometric\n    // to (b0, b1, b2), and it should define a quadratic\n    // bezier segment aligned with y = x^2\n    vec3 dst0 = vec3(x0, x0 * x0, 0.0);\n    vec3 dst1 = vec3(x1, x0 * x2, 0.0);\n    vec3 dst2 = vec3(x2, x2 * x2, 0.0);\n    return map_triangles(b0, b1, b2, dst0, dst1, dst2);\n}\n"
  },
  {
    "path": "manimlib/shaders/mandelbrot_fractal/frag.glsl",
    "content": "#version 330\n\nuniform vec2 parameter;\nuniform float opacity;\nuniform float n_steps;\nuniform float mandelbrot;\n\nuniform vec3 color0;\nuniform vec3 color1;\nuniform vec3 color2;\nuniform vec3 color3;\nuniform vec3 color4;\nuniform vec3 color5;\nuniform vec3 color6;\nuniform vec3 color7;\nuniform vec3 color8;\n\nin vec3 xyz_coords;\n\nout vec4 frag_color;\n\n#INSERT finalize_color.glsl\n#INSERT complex_functions.glsl\n\nconst int MAX_DEGREE = 5;\n\nvoid main() {\n    vec3 color_map[9] = vec3[9](\n        color0, color1, color2, color3,\n        color4, color5, color6, color7, color8\n    );\n    vec3 color;\n\n    vec2 z;\n    vec2 c;\n\n    if(bool(mandelbrot)){\n        c = xyz_coords.xy;\n        z = vec2(0.0, 0.0);\n    }else{\n        c = parameter;\n        z = xyz_coords.xy;\n    }\n\n    float outer_bound = 2.0;\n    bool stable = true;\n    for(int n = 0; n < int(n_steps); n++){\n        z = complex_mult(z, z) + c;\n        if(length(z) > outer_bound){\n            float float_n = float(n);\n            float_n += log(outer_bound) / log(length(z));\n            float_n += 0.5 * length(c);\n            color = float_to_color(sqrt(float_n), 1.5, 8.0, color_map);\n            stable = false;\n            break;\n        }\n    }\n    if(stable){\n        color = vec3(0.0, 0.0, 0.0);\n    }\n\n    frag_color = finalize_color(\n        vec4(color, opacity),\n        xyz_coords,\n        vec3(0.0, 0.0, 1.0)\n    );\n }"
  },
  {
    "path": "manimlib/shaders/mandelbrot_fractal/vert.glsl",
    "content": "#version 330\n\nin vec3 point;\nout vec3 xyz_coords;\n\nuniform float scale_factor;\nuniform vec3 offset;\n\n#INSERT emit_gl_Position.glsl\n\nvoid main(){\n    xyz_coords = (point - offset) / scale_factor;\n    emit_gl_Position(point);\n}"
  },
  {
    "path": "manimlib/shaders/newton_fractal/frag.glsl",
    "content": "#version 330\n\nuniform vec4 color0;\nuniform vec4 color1;\nuniform vec4 color2;\nuniform vec4 color3;\nuniform vec4 color4;\n\nuniform vec2 coef0;\nuniform vec2 coef1;\nuniform vec2 coef2;\nuniform vec2 coef3;\nuniform vec2 coef4;\nuniform vec2 coef5;\n\nuniform vec2 root0;\nuniform vec2 root1;\nuniform vec2 root2;\nuniform vec2 root3;\nuniform vec2 root4;\n\nuniform float n_roots;\nuniform float n_steps;\nuniform float julia_highlight;\nuniform float saturation_factor;\nuniform float black_for_cycles;\nuniform float is_parameter_space;\n\nin vec3 xyz_coords;\n\nout vec4 frag_color;\n\n#INSERT finalize_color.glsl\n#INSERT complex_functions.glsl\n\nconst int MAX_DEGREE = 5;\nconst float CLOSE_ENOUGH = 1e-3;\n\n\nvec2 poly(vec2 z, vec2[MAX_DEGREE + 1] coefs){\n    vec2 result = vec2(0.0);\n    for(int n = 0; n < int(n_roots) + 1; n++){\n        result += complex_mult(coefs[n], complex_pow(z, n));\n    }\n    return result;\n}\n\nvec2 dpoly(vec2 z, vec2[MAX_DEGREE + 1] coefs){\n    vec2 result = vec2(0.0);\n    for(int n = 1; n < int(n_roots) + 1; n++){\n        result += n * complex_mult(coefs[n], complex_pow(z, n - 1));\n    }\n    return result;\n}\n\nvec2 seek_root(vec2 z, vec2[MAX_DEGREE + 1] coefs, int max_steps, out float n_iters){\n    float last_len;\n    float curr_len;\n    float threshold = CLOSE_ENOUGH;\n\n    for(int i = 0; i < max_steps; i++){\n        last_len = curr_len;\n        n_iters = float(i);\n        vec2 step = complex_div(poly(z, coefs), dpoly(z, coefs));\n        curr_len = length(step);\n        if(curr_len < threshold){\n            break;\n        }\n        z = z - step;\n    }\n    n_iters -= log(curr_len) / log(threshold);\n\n    return z;\n}\n\n\nvoid main() {\n    vec2[MAX_DEGREE + 1] coefs = vec2[MAX_DEGREE + 1](coef0, coef1, coef2, coef3, coef4, coef5);\n    vec2[MAX_DEGREE] roots = vec2[MAX_DEGREE](root0, root1, root2, root3, root4);\n    vec4[MAX_DEGREE] colors = vec4[MAX_DEGREE](color0, color1, color2, color3, color4);\n\n    vec2 z = xyz_coords.xy;\n\n    if(is_parameter_space > 0){\n        // In this case, pixel should correspond to one of the roots\n        roots[2] = xyz_coords.xy;\n        vec2 r0 = roots[0];\n        vec2 r1 = roots[1];\n        vec2 r2 = roots[2];\n\n        // It is assumed that the polynomial is cubid...\n        coefs[0] = -complex_mult(complex_mult(r0, r1), r2);\n        coefs[1] = complex_mult(r0, r1) + complex_mult(r0, r2) + complex_mult(r1, r2);\n        coefs[2] = -(r0 + r1 + r2);\n        coefs[3] = vec2(1.0, 0.0);\n\n        // Seed value is always center of the roots\n        z = -coefs[2] / 3.0;\n    }\n\n    float n_iters;\n    vec2 found_root = seek_root(z, coefs, int(n_steps), n_iters);\n\n    vec4 color = vec4(0.0);\n    float min_dist = 1e10;\n    float dist;\n    for(int i = 0; i < int(n_roots); i++){\n        dist = distance(roots[i], found_root);\n        if(dist < min_dist){\n            min_dist = dist;\n            color = colors[i];\n        }\n    }\n    color *= 1.0 + (0.01 * saturation_factor) * (n_iters - 2 * saturation_factor);\n\n    if(black_for_cycles > 0 && min_dist > CLOSE_ENOUGH){\n        color = vec4(0.0, 0.0, 0.0, 1.0);\n    }\n\n    if(julia_highlight > 0.0){\n        float radius = julia_highlight;\n        vec2[4] samples = vec2[4](\n            z + vec2(radius, 0.0),\n            z + vec2(-radius, 0.0),\n            z + vec2(0.0, radius),\n            z + vec2(0.0, -radius)\n        );\n        for(int i = 0; i < 4; i++){\n            for(int j = 0; j < n_steps; j++){\n                vec2 z = samples[i];\n                z = z - complex_div(poly(z, coefs), dpoly(z, coefs));\n                samples[i] = z;\n            }\n        }\n        float max_dist = 0.0;\n        for(int i = 0; i < 4; i++){\n            max_dist = max(max_dist, distance(samples[i], samples[(i + 1) % 4]));\n        }\n        color *= 1.0 * smoothstep(0, 0.1, max_dist);\n    }\n\n    frag_color = finalize_color(\n        color,\n        xyz_coords,\n        vec3(0.0, 0.0, 1.0)\n    );\n }"
  },
  {
    "path": "manimlib/shaders/newton_fractal/vert.glsl",
    "content": "#version 330\n\nin vec3 point;\nout vec3 xyz_coords;\n\nuniform float scale_factor;\nuniform vec3 offset;\n\n#INSERT emit_gl_Position.glsl\n\nvoid main(){\n    xyz_coords = (point - offset) / scale_factor;\n    emit_gl_Position(point);\n}"
  },
  {
    "path": "manimlib/shaders/quadratic_bezier/depth/frag.glsl",
    "content": "#version 330\n\nout float frag_depth;\n\nvoid main() {\n    frag_depth = gl_FragCoord.z;\n}\n"
  },
  {
    "path": "manimlib/shaders/quadratic_bezier/depth/geom.glsl",
    "content": "#version 330\n\nlayout (triangles) in;\nlayout (triangle_strip, max_vertices = 6) out;\n\nin vec3 verts[3];\nin vec3 v_base_point[3];\n\nout float depth;\n\n#INSERT emit_gl_Position.glsl\n\n\nvoid emit_triangle(vec3 points[3]){\n    for(int i = 0; i < 3; i++){\n        emit_gl_Position(points[i]);\n        EmitVertex();\n    }\n    EndPrimitive();\n}\n\n\nvoid main(){\n    // Curves are marked as ended when the handle after\n    // the first anchor is set equal to that anchor\n    if (verts[0] == verts[1]) return;\n\n    // Emit two triangles\n    emit_triangle(vec3[3](v_base_point[0], verts[0], verts[2]));\n    emit_triangle(vec3[3](verts[0], verts[1], verts[2]));\n}\n\n"
  },
  {
    "path": "manimlib/shaders/quadratic_bezier/depth/vert.glsl",
    "content": "#version 330\n\nin vec3 point;\nin vec3 base_normal;\n\nout vec3 verts;\nout vec3 v_base_point;\n\nvoid main(){\n    verts = point;\n    v_base_point = base_normal;\n}"
  },
  {
    "path": "manimlib/shaders/quadratic_bezier/fill/frag.glsl",
    "content": "#version 330\n\nuniform bool winding;\n\nin vec4 color;\nin float fill_all;\nin float orientation;\nin vec2 uv_coords;\n\nout vec4 frag_color;\n\nvoid main() {\n    if (color.a == 0) discard;\n    frag_color = color;\n    /*\n    We want negatively oriented triangles to be canceled with positively\n    oriented ones. The easiest way to do this is to give them negative alpha,\n    and change the blend function to just add them. However, this messes with\n    usual blending, so instead the following line is meant to let this canceling\n    work even for the normal blending equation:\n\n    (1 - alpha) * dst + alpha * src\n\n    We want the effect of blending with a positively oriented triangle followed\n    by a negatively oriented one to return to whatever the original frag value\n    was. You can work out this will work if the alpha for negative orientations\n    is changed to -alpha / (1 - alpha). This has a singularity at alpha = 1,\n    so we cap it at a value very close to 1. Effectively, the purpose of this\n    cap is to make sure the original fragment color can be recovered even after\n    blending with an (alpha = 1) color.\n    */\n    float a = 0.95 * frag_color.a;\n    if(orientation < 0) a = -a / (1 - a);\n    frag_color.a = a;\n\n    if (bool(fill_all)) return;\n\n    float x = uv_coords.x;\n    float y = uv_coords.y;\n    float Fxy = (y - x * x);\n    if(Fxy < 0) discard;\n}\n"
  },
  {
    "path": "manimlib/shaders/quadratic_bezier/fill/geom.glsl",
    "content": "#version 330\n\nlayout (triangles) in;\nlayout (triangle_strip, max_vertices = 6) out;\n\nin vec3 verts[3];\nin vec4 v_color[3];\nin vec3 v_base_normal[3];\n\nout vec4 color;\nout float fill_all;\nout float orientation;\n// uv space is where the curve coincides with y = x^2\nout vec2 uv_coords;\n\n// A quadratic bezier curve with these points coincides with y = x^2\nconst vec2 SIMPLE_QUADRATIC[3] = vec2[3](\n    vec2(0.0, 0.0),\n    vec2(0.5, 0),\n    vec2(1.0, 1.0)\n);\n\n// Analog of import for manim only\n#INSERT emit_gl_Position.glsl\n#INSERT finalize_color.glsl\n\n\nvoid emit_triangle(vec3 points[3], vec4 v_color[3], vec3 unit_normal){\n    orientation = sign(determinant(mat3(\n        unit_normal,\n        points[1] - points[0],\n        points[2] - points[0]\n    )));\n\n    for(int i = 0; i < 3; i++){\n        uv_coords = SIMPLE_QUADRATIC[i];\n        color = finalize_color(v_color[i], points[i], unit_normal);\n        emit_gl_Position(points[i]);\n        EmitVertex();\n    }\n    EndPrimitive();\n}\n\n\nvoid main(){\n    // Curves are marked as ended when the handle after\n    // the first anchor is set equal to that anchor\n    if (verts[0] == verts[1]) return;\n\n    // Check zero fill\n    if (vec3(v_color[0].a, v_color[1].a, v_color[2].a) == vec3(0.0, 0.0, 0.0)) return;\n\n    vec3 base_point = v_base_normal[0];\n    vec3 unit_normal = v_base_normal[1];\n    // Emit main triangle\n    fill_all = 1.0;\n    emit_triangle(\n        vec3[3](base_point, verts[0], verts[2]),\n        vec4[3](v_color[1], v_color[0], v_color[2]),\n        unit_normal\n    );\n    // Edge triangle\n    fill_all = 0.0;\n    emit_triangle(\n        vec3[3](verts[0], verts[1], verts[2]),\n        vec4[3](v_color[0], v_color[1], v_color[2]),\n        unit_normal\n    );\n}\n\n"
  },
  {
    "path": "manimlib/shaders/quadratic_bezier/fill/vert.glsl",
    "content": "#version 330\n\nin vec3 point;\nin vec4 fill_rgba;\nin vec3 base_normal;\n\nout vec3 verts;  // Bezier control point\nout vec4 v_color;\nout vec3 v_base_normal;\n\nvoid main(){\n    verts = point;\n    v_color = fill_rgba;\n    v_base_normal = base_normal;\n}"
  },
  {
    "path": "manimlib/shaders/quadratic_bezier/stroke/frag.glsl",
    "content": "#version 330\n\n// Distance to the curve, and half the curve width, both as\n// a ratio of the antialias width\nin float dist_to_aaw;\nin float half_width_to_aaw;\nin vec4 color;\n\nout vec4 frag_color;\n\nvoid main() {\n    frag_color = color;\n    // sdf for the region around the curve we wish to color.\n    float signed_dist_to_region = abs(dist_to_aaw) - half_width_to_aaw;\n    frag_color.a *= smoothstep(0.5, -0.5, signed_dist_to_region);\n    // This line is replaced in VShaderWrapper\n    // MODIFY FRAG COLOR\n}"
  },
  {
    "path": "manimlib/shaders/quadratic_bezier/stroke/geom.glsl",
    "content": "#version 330\n\nlayout (triangles) in;\nlayout (triangle_strip, max_vertices = 64) out;  // Related to MAX_STEPS below\n\nuniform float anti_alias_width;\nuniform float flat_stroke;\nuniform float pixel_size;\nuniform float joint_type;\nuniform float frame_scale;\n\nin vec3 verts[3];\n\nin float v_joint_angle[3];\nin float v_stroke_width[3];\nin vec4 v_color[3];\nin vec3 v_unit_normal[3];\n\nout vec4 color;\nout float dist_to_aaw;\nout float half_width_to_aaw;\n\n// Codes for joint types\nconst int NO_JOINT = 0;\nconst int AUTO_JOINT = 1;\nconst int BEVEL_JOINT = 2;\nconst int MITER_JOINT = 3;\n\n// When the cosine of the angle between\n// two vectors is larger than this, we\n// consider them aligned\nconst float COS_THRESHOLD = 0.999;\n// Used to determine how many lines to break the curve into\nconst float POLYLINE_FACTOR = 100;\nconst int MAX_STEPS = 32;\nconst float MITER_COS_ANGLE_THRESHOLD = -0.8;\n\n#INSERT emit_gl_Position.glsl\n#INSERT finalize_color.glsl\n\n\n\nvec3 point_on_quadratic(float t, vec3 c0, vec3 c1, vec3 c2){\n    return c0 + c1 * t + c2 * t * t;\n}\n\n\nvec3 tangent_on_quadratic(float t, vec3 c1, vec3 c2){\n    return c1 + 2 * c2 * t;\n}\n\n\nvec3 project(vec3 vect, vec3 unit_normal){\n    /* Project the vector onto the plane perpendicular to a given unit normal */\n    return vect - dot(vect, unit_normal) * unit_normal;\n}\n\n\nvec3 rotate_vector(vec3 vect, vec3 unit_normal, float angle){\n    vec3 perp = cross(unit_normal, vect);\n    return cos(angle) * vect + sin(angle) * perp;\n}\n\n\nvec3 step_to_corner(vec3 point, vec3 tangent, vec3 unit_normal, float joint_angle, bool inside_curve, bool draw_flat){\n    /*\n    Step the the left of a curve.\n    First a perpendicular direction is calculated, then it is adjusted\n    so as to make a joint.\n    */\n    vec3 unit_tan = normalize(draw_flat ? tangent : project(tangent, unit_normal));\n\n    // Step to stroke width bound should be perpendicular\n    // both to the tangent and the normal direction\n    vec3 step = normalize(cross(unit_normal, unit_tan));\n\n    // For non-flat stroke, there can be glitches when the tangent direction\n    // lines up very closely with the direction to the camera, treated here\n    // as the unit normal. To avoid those, this smoothly transitions to a step\n    // direction perpendicular to the true curve normal.\n    if(joint_angle != 0){\n        float alignment = abs(dot(normalize(tangent), unit_normal));\n        float alignment_threshold = 0.97;  // This could maybe be chosen in a more principled way based on stroke width\n        if (alignment > alignment_threshold) {\n            vec3 perp = normalize(cross(v_unit_normal[1], tangent));\n            step = mix(step, project(step, perp), smoothstep(alignment_threshold, 1.0, alignment));\n        }\n    }\n\n    if (inside_curve || int(joint_type) == NO_JOINT) return step;\n\n    float cos_angle = cos(joint_angle);\n    float sin_angle = sin(joint_angle);\n\n    if (abs(cos_angle) > COS_THRESHOLD) return step;\n\n    // Below here, figure out the adjustment to bevel or miter a joint\n    if (!draw_flat){\n        // Figure out what joint product would be for everything projected onto\n        // the plane perpendicular to the normal direction (which here would be to_camera)\n        step = normalize(cross(unit_normal, unit_tan));  // Back to original step\n        vec3 adj_tan = rotate_vector(tangent, v_unit_normal[1], joint_angle);\n        adj_tan = project(adj_tan, unit_normal);\n        cos_angle = dot(unit_tan, normalize(adj_tan));\n        sin_angle = sqrt(1 - cos_angle * cos_angle) * sign(joint_angle) * sign(dot(unit_normal, v_unit_normal[1]));\n    }\n\n    // If joint type is auto, it will bevel for cos(angle) > MITER_COS_ANGLE_THRESHOLD,\n    // and smoothly transition to miter for those with sharper angles\n    float miter_factor;\n    if (joint_type == BEVEL_JOINT){\n        miter_factor = 0.0;\n    }else if (joint_type == MITER_JOINT){\n        miter_factor = 1.0;\n    }else {\n        float mcat1 = MITER_COS_ANGLE_THRESHOLD;\n        float mcat2 = mix(mcat1, -1.0, 0.5);\n        miter_factor = smoothstep(mcat1, mcat2, cos_angle);\n    }\n\n    float shift = (cos_angle + mix(-1, 1, miter_factor)) / sin_angle;\n    return step + shift * unit_tan;\n}\n\n\nvoid emit_point_with_width(\n    vec3 point,\n    vec3 tangent,\n    float joint_angle,\n    float width,\n    vec4 joint_color,\n    bool inside_curve,\n    bool draw_flat\n){\n    // Find unit normal\n    vec3 unit_normal = draw_flat ? v_unit_normal[1] : normalize(camera_position - point);\n\n    // Set styling\n    color = finalize_color(joint_color, point, unit_normal);\n\n    // Figure out the step from the point to the corners of the\n    // triangle strip around the polyline\n    vec3 step = step_to_corner(point, tangent, unit_normal, joint_angle, inside_curve, draw_flat);\n    float aaw = max(anti_alias_width * pixel_size, 1e-8);\n\n    // Emit two corners\n    // The frag shader will receive a value from -1 to 1,\n    // reflecting where in the stroke that point is\n    for (int sign = -1; sign <= 1; sign += 2){\n        float dist_to_curve = sign * 0.5 * (width + aaw);\n        emit_gl_Position(point + dist_to_curve * step);\n        half_width_to_aaw = 0.5 * width / aaw;\n        dist_to_aaw = dist_to_curve / aaw;\n        EmitVertex();\n    }\n}\n\n\nvoid main() {\n    // Curves are marked as ended when the handle after\n    // the first anchor is set equal to that anchor\n    if (verts[0] == verts[1]) return;\n\n    // Check null stroke\n    if (vec3(v_stroke_width[0], v_stroke_width[1], v_stroke_width[2]) == vec3(0.0, 0.0, 0.0)) return;\n    if (vec3(v_color[0].a, v_color[1].a, v_color[2].a) == vec3(0.0, 0.0, 0.0)) return;\n\n    bool draw_flat = bool(flat_stroke) || bool(is_fixed_in_frame);\n\n    // Coefficients such that the quadratic bezier is c0 + c1 * t  + c2 * t^2\n    vec3 c0 = verts[0];\n    vec3 c1 = 2 * (verts[1] - verts[0]);\n    vec3 c2 = verts[0] - 2 * verts[1] + verts[2];\n\n    // Estimate how many line segment the curve should be divided into\n    // based on the area of the triangle defined by these control points\n    float area = 0.5 * length(cross(verts[1] - verts[0], verts[2] - verts[0]));\n    int count = int(round(POLYLINE_FACTOR * sqrt(area) / frame_scale));\n    int n_steps = min(2 + count, MAX_STEPS);\n\n    // Emit vertex pairs aroudn subdivided points\n    for (int i = 0; i < MAX_STEPS; i++){\n        if (i >= n_steps) break;\n        float t = float(i) / (n_steps - 1);\n\n        // Point and tangent\n        vec3 point = point_on_quadratic(t, c0, c1, c2);\n        vec3 tangent = tangent_on_quadratic(t, c1, c2);\n\n        // Style\n        float stroke_width = mix(v_stroke_width[0], v_stroke_width[2], t);\n        vec4 color = mix(v_color[0], v_color[2], t);\n\n        // This is sent along to prevent needless joint creation\n        bool inside_curve = (i > 0 && i < n_steps - 1);\n\n        // Use middle joint product for inner points, flip sign for first one's cross product component\n        float joint_angle;\n        if (i == 0){\n            joint_angle = -v_joint_angle[0];\n        }\n        else if (inside_curve){\n            joint_angle = 0;\n        }\n        else {\n            joint_angle = v_joint_angle[2];\n        }\n\n        emit_point_with_width(\n            point, tangent, joint_angle,\n            stroke_width, color,\n            inside_curve, draw_flat\n        );\n    }\n    EndPrimitive();\n}"
  },
  {
    "path": "manimlib/shaders/quadratic_bezier/stroke/vert.glsl",
    "content": "#version 330\n\nuniform float frame_scale;\nuniform float is_fixed_in_frame;\nuniform float scale_stroke_with_zoom;\n\nin vec3 point;\nin vec4 stroke_rgba;\nin float stroke_width;\nin float joint_angle;\nin vec3 unit_normal;\n\n// Bezier control point\nout vec3 verts;\n\nout vec4 v_color;\nout float v_stroke_width;\nout float v_joint_angle;\nout vec3 v_unit_normal;\n\nconst float STROKE_WIDTH_CONVERSION = 0.01;\n\nvoid main(){\n    verts = point;\n    v_color = stroke_rgba;\n    v_stroke_width = STROKE_WIDTH_CONVERSION * stroke_width * mix(frame_scale, 1, scale_stroke_with_zoom);\n    v_joint_angle = joint_angle;\n    v_unit_normal = unit_normal;\n}"
  },
  {
    "path": "manimlib/shaders/simple_vert.glsl",
    "content": "#version 330\n\nin vec3 point;\n\n#INSERT emit_gl_Position.glsl\n\nvoid main(){\n    emit_gl_Position(point);\n}"
  },
  {
    "path": "manimlib/shaders/surface/frag.glsl",
    "content": "#version 330\n\nin vec4 v_color;\nout vec4 frag_color;\n\nvoid main() {\n    frag_color = v_color;\n}"
  },
  {
    "path": "manimlib/shaders/surface/vert.glsl",
    "content": "#version 330\n\nin vec3 point;\nin vec3 d_normal_point;\nin vec4 rgba;\n\nout vec4 v_color;\n\n#INSERT emit_gl_Position.glsl\n#INSERT get_unit_normal.glsl\n#INSERT finalize_color.glsl\n\nconst float EPSILON = 1e-10;\n\nvoid main(){\n    emit_gl_Position(point);\n    vec3 unit_normal = normalize(d_normal_point - point);\n    v_color = finalize_color(rgba, point, unit_normal);\n}"
  },
  {
    "path": "manimlib/shaders/textured_surface/frag.glsl",
    "content": "#version 330\n\nuniform sampler2D LightTexture;\nuniform sampler2D DarkTexture;\nuniform float num_textures;\n\nin vec3 v_point;\nin vec3 v_unit_normal;\nin vec2 v_im_coords;\nin float v_opacity;\n\nout vec4 frag_color;\n\n#INSERT finalize_color.glsl\n\nconst float dark_shift = 0.2;\n\nvoid main() {\n    vec4 color = texture(LightTexture, v_im_coords);\n    if(num_textures == 2.0){\n        vec4 dark_color = texture(DarkTexture, v_im_coords);\n        float dp = dot(\n            normalize(light_position - v_point),\n            v_unit_normal\n        );\n        float alpha = smoothstep(-dark_shift, dark_shift, dp);\n        color = mix(dark_color, color, alpha);\n    }\n    if (color.a == 0) discard;\n\n    frag_color = finalize_color(\n        color,\n        v_point,\n        v_unit_normal\n    );\n    frag_color.a = v_opacity;\n}"
  },
  {
    "path": "manimlib/shaders/textured_surface/vert.glsl",
    "content": "#version 330\n\nin vec3 point;\nin vec3 d_normal_point;\nin vec2 im_coords;\nin float opacity;\n\nout vec3 v_point;\nout vec3 v_unit_normal;\nout vec2 v_im_coords;\nout float v_opacity;\n\nuniform float is_sphere;\nuniform vec3 center;\n\n#INSERT emit_gl_Position.glsl\n#INSERT get_unit_normal.glsl\n\nconst float EPSILON = 1e-10;\n\nvoid main(){\n    v_point = point;\n    v_unit_normal = normalize(d_normal_point - point);;\n    v_im_coords = im_coords;\n    v_opacity = opacity;\n    emit_gl_Position(point);\n}"
  },
  {
    "path": "manimlib/shaders/true_dot/frag.glsl",
    "content": "#version 330\n\nuniform float glow_factor;\nuniform mat4 perspective;\n\nin vec4 color;\nin float scaled_aaw;\nin vec3 point;\nin vec3 to_cam;\nin vec3 center;\nin float radius;\nin vec2 uv_coords;\n\nout vec4 frag_color;\n\n// This includes a declaration of uniform vec3 shading\n#INSERT finalize_color.glsl\n\nvoid main() {\n    float r = length(uv_coords.xy);\n    if(r > 1.0) discard;\n\n    frag_color = color;\n\n    if(glow_factor > 0){\n        frag_color.a *= pow(1 - r, glow_factor);\n    }\n\n    if(shading != vec3(0.0)){\n        vec3 point_3d = point + radius * sqrt(1 - r * r) * to_cam;\n        vec3 normal = normalize(point_3d - center);\n        frag_color = finalize_color(frag_color, point_3d, normal);\n    }\n\n    frag_color.a *= smoothstep(1.0, 1.0 - scaled_aaw, r);\n}"
  },
  {
    "path": "manimlib/shaders/true_dot/geom.glsl",
    "content": "#version 330\n\nlayout (points) in;\nlayout (triangle_strip, max_vertices = 4) out;\n\nuniform float pixel_size;\nuniform float anti_alias_width;\nuniform float frame_scale;\nuniform vec3 camera_position;\n\nin vec3 v_point[1];\nin float v_radius[1];\nin vec4 v_rgba[1];\n\nout vec4 color;\nout float scaled_aaw;\nout vec3 point;\nout vec3 to_cam;\nout vec3 center;\nout float radius;\nout vec2 uv_coords;\n\n#INSERT emit_gl_Position.glsl\n\nvoid main(){\n    color = v_rgba[0];\n    radius = v_radius[0];\n    center = v_point[0];\n    scaled_aaw = (anti_alias_width * pixel_size) / v_radius[0];\n\n    to_cam = normalize(camera_position - v_point[0]);\n    vec3 right = v_radius[0] * normalize(cross(vec3(0, 1, 1), to_cam));\n    vec3 up = v_radius[0] * normalize(cross(to_cam, right));\n\n    for(int i = -1; i < 2; i += 2){\n        for(int j = -1; j < 2; j += 2){\n            point = v_point[0] + i * right + j * up;\n            uv_coords = vec2(i, j);\n            emit_gl_Position(point);\n            EmitVertex();\n        }\n    }\n    EndPrimitive();\n}"
  },
  {
    "path": "manimlib/shaders/true_dot/vert.glsl",
    "content": "#version 330\n\nin vec3 point;\nin float radius;\nin vec4 rgba;\n\nout vec3 v_point;\nout float v_radius;\nout vec4 v_rgba;\n\n\nvoid main(){\n    v_point = point;\n    v_radius = radius;\n    v_rgba = rgba;\n}"
  },
  {
    "path": "manimlib/tex_templates.yml",
    "content": "# Classical TeX templates\n\ndefault:\n  description: \"\"\n  compiler: latex\n  preamble: |-\n    \\usepackage[english]{babel}\n    \\usepackage[utf8]{inputenc}\n    \\usepackage[T1]{fontenc}\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{dsfont}\n    \\usepackage{setspace}\n    \\usepackage{tipa}\n    \\usepackage{relsize}\n    \\usepackage{textcomp}\n    \\usepackage{mathrsfs}\n    \\usepackage{calligra}\n    \\usepackage{wasysym}\n    \\usepackage{ragged2e}\n    \\usepackage{physics}\n    \\usepackage{xcolor}\n    \\usepackage{microtype}\n    \\usepackage{pifont}\n    \\DisableLigatures{encoding = *, family = * }\n    \\linespread{1}\n    %% Borrowed from https://tex.stackexchange.com/questions/6058/making-a-shorter-minus\n    \\DeclareMathSymbol{\\minus}{\\mathbin}{AMSa}{\"39}\nctex:\n  description: \"\"\n  compiler: xelatex\n  preamble: |-\n    \\usepackage[UTF8]{ctex}\n    \\usepackage[english]{babel}\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{dsfont}\n    \\usepackage{setspace}\n    \\usepackage{tipa}\n    \\usepackage{relsize}\n    \\usepackage{textcomp}\n    \\usepackage{mathrsfs}\n    \\usepackage{calligra}\n    \\usepackage{wasysym}\n    \\usepackage{ragged2e}\n    \\usepackage{physics}\n    \\usepackage{xcolor}\n    \\usepackage{microtype}\n    \\usepackage{fontspec}\n    \\usepackage{xeCJK}\n    \\setmainfont{Microsoft YaHei}\n    \\linespread{1}\n\n# Simplified TeX templates\n\nbasic:\n  description: \"\"\n  compiler: latex\n  preamble: |-\n    \\usepackage[english]{babel}\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n\nbasic_ctex:\n  description: \"\"\n  compiler: xelatex\n  preamble: |-\n    \\usepackage[UTF8]{ctex}\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n\nempty:\n  description: \"\"\n  compiler: latex\n  preamble: \"\"\n\nempty_ctex:\n  description: \"\"\n  compiler: xelatex\n  preamble: \"\"\n\n# A collection of TeX templates for the fonts described at\n# http://jf.burnol.free.fr/showcase.html\n\namerican_typewriter:\n  description: American Typewriter\n  compiler: xelatex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[no-math]{fontspec}\n    \\setmainfont[Mapping=tex-text]{American Typewriter}\n    \\usepackage[defaultmathsizes]{mathastext}\n\nantykwa:\n  description: Antykwa Poltawskiego (TX Fonts for Greek and math symbols)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[OT4,OT1]{fontenc}\n    \\usepackage{txfonts}\n    \\usepackage[upright]{txgreeks}\n    \\usepackage{antpolt}\n    \\usepackage[defaultmathsizes,nolessnomore]{mathastext}\n\napple_chancery:\n  description: Apple Chancery\n  compiler: xelatex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[no-math]{fontspec}\n    \\setmainfont[Mapping=tex-text]{Apple Chancery}\n    \\usepackage[defaultmathsizes]{mathastext}\n\nauriocus_kalligraphicus:\n  description: Auriocus Kalligraphicus (Symbol Greek)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\usepackage{aurical}\n    \\renewcommand{\\rmdefault}{AuriocusKalligraphicus}\n    \\usepackage[symbolgreek]{mathastext}\n\nbaskervald_adf_fourier:\n  description: Baskervald ADF with Fourier\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[upright]{fourier}\n    \\usepackage{baskervald}\n    \\usepackage[defaultmathsizes,noasterisk]{mathastext}\n\nbaskerville_it:\n  description: Baskerville (Italic)\n  compiler: xelatex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[no-math]{fontspec}\n    \\setmainfont[Mapping=tex-text]{Baskerville}\n    \\usepackage[defaultmathsizes,italic]{mathastext}\n\nbiolinum:\n  description: Biolinum\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage{txfonts}\n    \\usepackage[upright]{txgreeks}\n    \\usepackage[no-math]{fontspec}\n    \\setmainfont[Mapping=tex-text]{Minion Pro}\n    \\setsansfont[Mapping=tex-text,Scale=MatchUppercase]{Myriad Pro}\n    \\renewcommand\\familydefault\\sfdefault\n    \\usepackage[defaultmathsizes]{mathastext}\n    \\renewcommand\\familydefault\\rmdefault\n\nbrushscriptx:\n  description: BrushScriptX-Italic (PX math and Greek)\n  compiler: xelatex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\usepackage{pxfonts}\n    \\renewcommand{\\rmdefault}{pbsi}\n    \\renewcommand{\\mddefault}{xl}\n    \\renewcommand{\\bfdefault}{xl}\n    \\usepackage[defaultmathsizes,noasterisk]{mathastext}\n    \\boldmath\n\nchalkboard_se:\n  description: Chalkboard SE\n  compiler: xelatex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[no-math]{fontspec}\n    \\setmainfont[Mapping=tex-text]{Chalkboard SE}\n    \\usepackage[defaultmathsizes]{mathastext}\n\nchalkduster:\n  description: Chalkduster\n  compiler: lualatex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[no-math]{fontspec}\n    \\setmainfont[Mapping=tex-text]{Chalkduster}\n    \\usepackage[defaultmathsizes]{mathastext}\n\ncomfortaa:\n  description: Comfortaa\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[default]{comfortaa}\n    \\usepackage[LGRgreek,defaultmathsizes,noasterisk]{mathastext}\n    \\let\\varphi\\phi\n    \\linespread{1.06}\n\ncomic_sans:\n  description: Comic Sans MS\n  compiler: xelatex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[no-math]{fontspec}\n    \\setmainfont[Mapping=tex-text]{Comic Sans MS}\n    \\usepackage[defaultmathsizes]{mathastext}\n\ndroid_sans:\n  description: Droid Sans\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\usepackage[default]{droidsans}\n    \\usepackage[LGRgreek]{mathastext}\n    \\let\\varepsilon\\epsilon\n\ndroid_sans_it:\n  description: Droid Sans (Italic)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\usepackage[default]{droidsans}\n    \\usepackage[LGRgreek,defaultmathsizes,italic]{mathastext}\n    \\let\\varphi\\phi\n\ndroid_serif:\n  description: Droid Serif\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\usepackage[default]{droidserif}\n    \\usepackage[LGRgreek]{mathastext}\n    \\let\\varepsilon\\epsilon\n\ndroid_serif_px_it:\n  description: Droid Serif (PX math symbols) (Italic)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\usepackage{pxfonts}\n    \\usepackage[default]{droidserif}\n    \\usepackage[LGRgreek,defaultmathsizes,italic,basic]{mathastext}\n    \\let\\varphi\\phi\n\necf_augie:\n  description: ECF Augie (Euler Greek)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\renewcommand\\familydefault{fau}\n    \\usepackage[defaultmathsizes,eulergreek]{mathastext}\n\necf_jd:\n  description: ECF JD (with TX fonts)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage{txfonts}\n    \\usepackage[upright]{txgreeks}\n    \\renewcommand\\familydefault{fjd}\n    \\usepackage{mathastext}\n    \\mathversion{bold}\n\necf_skeetch:\n  description: ECF Skeetch (CM Greek)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\DeclareFontFamily{T1}{fsk}{}\n    \\DeclareFontShape{T1}{fsk}{m}{n}{<->s*[1.315] fskmw8t}{}\n    \\renewcommand\\rmdefault{fsk}\n    \\usepackage[noendash,defaultmathsizes,nohbar,defaultimath]{mathastext}\n\necf_tall_paul:\n  description: ECF Tall Paul (with Symbol font)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\DeclareFontFamily{T1}{ftp}{}\n    \\DeclareFontShape{T1}{ftp}{m}{n}{<->s*[1.4] ftpmw8t}{}\n    \\renewcommand\\familydefault{ftp}\n    \\usepackage[symbol]{mathastext}\n    \\let\\infty\\inftypsy\n\necf_webster:\n  description: ECF Webster (with TX fonts)\n  compiler: xelatex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage{txfonts}\n    \\usepackage[upright]{txgreeks}\n    \\renewcommand\\familydefault{fwb}\n    \\usepackage{mathastext}\n    \\renewcommand{\\int}{\\intop\\limits}\n    \\linespread{1.5}\n    \\mathversion{bold}\n\nelectrum_adf:\n  description: Electrum ADF (CM Greek)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\usepackage[LGRgreek,basic,defaultmathsizes]{mathastext}\n    \\usepackage[lf]{electrum}\n    \\Mathastext\n    \\let\\varphi\\phi\n\nepigrafica:\n  description: Epigrafica\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[LGR,OT1]{fontenc}\n    \\usepackage{epigrafica}\n    \\usepackage[basic,LGRgreek,defaultmathsizes]{mathastext}\n    \\let\\varphi\\phi\n    \\linespread{1.2}\n\nfourier_utopia:\n  description: Fourier Utopia (Fourier upright Greek)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\usepackage[upright]{fourier}\n    \\usepackage{mathastext}\n\nfrench_cursive:\n  description: French Cursive (Euler Greek)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\usepackage[default]{frcursive}\n    \\usepackage[eulergreek,noplusnominus,noequal,nohbar,nolessnomore,noasterisk]{mathastext}\n\ngfs_bodoni:\n  description: GFS Bodoni\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\renewcommand{\\rmdefault}{bodoni}\n    \\usepackage[LGRgreek]{mathastext}\n    \\let\\varphi\\phi\n    \\linespread{1.06}\n\ngfs_didot:\n  description: GFS Didot (Italic)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\renewcommand\\rmdefault{udidot}\n    \\usepackage[LGRgreek,defaultmathsizes,italic]{mathastext}\n    \\let\\varphi\\phi\n\ngfs_neohellenic:\n  description: GFS NeoHellenic\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\renewcommand{\\rmdefault}{neohellenic}\n    \\usepackage[LGRgreek]{mathastext}\n    \\let\\varphi\\phi\n    \\linespread{1.06}\n\ngnu_freesans_tx:\n  description: GNU FreeSerif (and TX fonts symbols)\n  compiler: xelatex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[no-math]{fontspec}\n    \\usepackage{txfonts}\n    \\setmainfont[ExternalLocation,Mapping=tex-text,BoldFont=FreeSerifBold,ItalicFont=FreeSerifItalic,BoldItalicFont=FreeSerifBoldItalic]{FreeSerif}\n    \\usepackage[defaultmathsizes]{mathastext}\n\ngnu_freeserif_freesans:\n  description: GNU FreeSerif and FreeSans\n  compiler: xelatex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[no-math]{fontspec}\n    \\setmainfont[ExternalLocation,Mapping=tex-text,BoldFont=FreeSerifBold,ItalicFont=FreeSerifItalic,BoldItalicFont=FreeSerifBoldItalic]{FreeSerif}\n    \\setsansfont[ExternalLocation,Mapping=tex-text,BoldFont=FreeSansBold,ItalicFont=FreeSansOblique,BoldItalicFont=FreeSansBoldOblique,Scale=MatchLowercase]{FreeSans}\n    \\renewcommand{\\familydefault}{lmss}\n    \\usepackage[LGRgreek,defaultmathsizes,noasterisk]{mathastext}\n    \\renewcommand{\\familydefault}{\\sfdefault}\n    \\Mathastext\n    \\let\\varphi\\phi\n    \\renewcommand{\\familydefault}{\\rmdefault}\n\nhelvetica_fourier_it:\n  description: Helvetica with Fourier (Italic)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\usepackage[scaled]{helvet}\n    \\usepackage{fourier}\n    \\renewcommand{\\rmdefault}{phv}\n    \\usepackage[italic,defaultmathsizes,noasterisk]{mathastext}\n\nlatin_modern_tw:\n  description: Latin Modern Typewriter Proportional\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\usepackage[variablett]{lmodern}\n    \\renewcommand{\\rmdefault}{\\ttdefault}\n    \\usepackage[LGRgreek]{mathastext}\n    \\MTgreekfont{lmtt}\n    \\Mathastext\n    \\let\\varepsilon\\epsilon\n\nlatin_modern_tw_it:\n  description: Latin Modern Typewriter Proportional (CM Greek) (Italic)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\usepackage[variablett,nomath]{lmodern}\n    \\renewcommand{\\familydefault}{\\ttdefault}\n    \\usepackage[frenchmath]{mathastext}\n    \\linespread{1.08}\n\nlibertine:\n  description: Libertine\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\usepackage{libertine}\n    \\usepackage[greek=n]{libgreek}\n    \\usepackage[noasterisk,defaultmathsizes]{mathastext}\n\nlibris_adf_fourier:\n  description: Libris ADF with Fourier\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\usepackage[upright]{fourier}\n    \\usepackage{libris}\n    \\renewcommand{\\familydefault}{\\sfdefault}\n    \\usepackage[noasterisk]{mathastext}\n\nminion_pro_myriad_pro:\n  description: Minion Pro and Myriad Pro (and TX fonts symbols)\n  compiler: xelatex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\usepackage[default]{droidserif}\n    \\usepackage[LGRgreek]{mathastext}\n    \\let\\varepsilon\\epsilon\n\nminion_pro_tx:\n  description: Minion Pro (and TX fonts symbols)\n  compiler: xelatex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage{txfonts}\n    \\usepackage[no-math]{fontspec}\n    \\setmainfont[Mapping=tex-text]{Minion Pro}\n    \\usepackage[defaultmathsizes]{mathastext}\n\nnew_century_schoolbook:\n  description: New Century Schoolbook (Symbol Greek)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\usepackage{newcent}\n    \\usepackage[symbolgreek]{mathastext}\n    \\linespread{1.1}\n\nnew_century_schoolbook_px:\n  description: New Century Schoolbook (Symbol Greek, PX math symbols)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\usepackage{pxfonts}\n    \\usepackage{newcent}\n    \\usepackage[symbolgreek,defaultmathsizes]{mathastext}\n    \\linespread{1.06}\n\nnoteworthy_light:\n  description: Noteworthy Light\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[no-math]{fontspec}\n    \\setmainfont[Mapping=tex-text]{Noteworthy Light}\n    \\usepackage[defaultmathsizes]{mathastext}\n\npalatino:\n  description: Palatino (Symbol Greek)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\usepackage{palatino}\n    \\usepackage[symbolmax,defaultmathsizes]{mathastext}\n\npapyrus:\n  description: Papyrus\n  compiler: xelatex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[no-math]{fontspec}\n    \\setmainfont[Mapping=tex-text]{Papyrus}\n    \\usepackage[defaultmathsizes]{mathastext}\n\nromande_adf_fourier_it:\n  description: Romande ADF with Fourier (Italic)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\usepackage{fourier}\n    \\usepackage{romande}\n    \\usepackage[italic,defaultmathsizes,noasterisk]{mathastext}\n    \\renewcommand{\\itshape}{\\swashstyle}\n\nslitex:\n  description: SliTeX (Euler Greek)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\usepackage{tpslifonts}\n    \\usepackage[eulergreek,defaultmathsizes]{mathastext}\n    \\MTEulerScale{1.06}\n    \\linespread{1.2}\n\ntimes_fourier_it:\n  description: Times with Fourier (Italic)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage{fourier}\n    \\renewcommand{\\rmdefault}{ptm}\n    \\usepackage[italic,defaultmathsizes,noasterisk]{mathastext}\n\nurw_avant_garde:\n  description: URW Avant Garde (Symbol Greek)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\usepackage{avant}\n    \\renewcommand{\\familydefault}{\\sfdefault}\n    \\usepackage[symbolgreek,defaultmathsizes]{mathastext}\n\nurw_zapf_chancery:\n  description: URW Zapf Chancery (CM Greek)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\DeclareFontFamily{T1}{pzc}{}\n    \\DeclareFontShape{T1}{pzc}{mb}{it}{<->s*[1.2] pzcmi8t}{}\n    \\DeclareFontShape{T1}{pzc}{m}{it}{<->ssub * pzc/mb/it}{}\n    \\DeclareFontShape{T1}{pzc}{mb}{sl}{<->ssub * pzc/mb/it}{}\n    \\DeclareFontShape{T1}{pzc}{m}{sl}{<->ssub * pzc/mb/sl}{}\n    \\DeclareFontShape{T1}{pzc}{m}{n}{<->ssub * pzc/mb/it}{}\n    \\usepackage{chancery}\n    \\usepackage{mathastext}\n    \\linespread{1.05}\n    \\boldmath\n\nventuris_adf_fourier_it:\n  description: Venturis ADF with Fourier (Italic)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage{fourier}\n    \\usepackage[lf]{venturis}\n    \\usepackage[italic,defaultmathsizes,noasterisk]{mathastext}\n\nverdana_it:\n  description: Verdana (Italic)\n  compiler: xelatex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[no-math]{fontspec}\n    \\setmainfont[Mapping=tex-text]{Verdana}\n    \\usepackage[defaultmathsizes,italic]{mathastext}\n\nvollkorn:\n  description: Vollkorn (TX fonts for Greek and math symbols)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage[T1]{fontenc}\n    \\usepackage{txfonts}\n    \\usepackage[upright]{txgreeks}\n    \\usepackage{vollkorn}\n    \\usepackage[defaultmathsizes]{mathastext}\n\nvollkorn_fourier_it:\n  description: Vollkorn with Fourier (Italic)\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\usepackage{fourier}\n    \\usepackage{vollkorn}\n    \\usepackage[italic,nohbar]{mathastext}\n\nzapf_chancery:\n  description: Zapf Chancery\n  compiler: latex\n  preamble: |-\n    \\usepackage{amsmath}\n    \\usepackage{amssymb}\n    \\usepackage{xcolor}\n    \\DeclareFontFamily{T1}{pzc}{}\n    \\DeclareFontShape{T1}{pzc}{mb}{it}{<->s*[1.2] pzcmi8t}{}\n    \\DeclareFontShape{T1}{pzc}{m}{it}{<->ssub * pzc/mb/it}{}\n    \\usepackage{chancery}\n    \\renewcommand\\shapedefault\\itdefault\n    \\renewcommand\\bfdefault\\mddefault\n    \\usepackage[defaultmathsizes]{mathastext}\n    \\linespread{1.05}\n"
  },
  {
    "path": "manimlib/typing.py",
    "content": "from typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Union, Tuple, Annotated, Literal, Iterable, Dict\n    from colour import Color\n    import numpy as np\n    import re\n\n    try:\n        from typing_extensions import Self\n    except ImportError:\n        from typing import Self\n\n    # Abbreviations for a common types\n    ManimColor = Union[str, Color, None]\n    RangeSpecifier = Tuple[float, float, float] | Tuple[float, float]\n\n\n    Span = tuple[int, int]\n    SingleSelector = Union[\n        str,\n        re.Pattern,\n        tuple[Union[int, None], Union[int, None]],\n    ]\n    Selector = Union[SingleSelector, Iterable[SingleSelector]]\n\n    UniformDict = Dict[str, float | bool | np.ndarray | tuple]\n\n    # These are various alternate names for np.ndarray meant to specify\n    # certain shapes.\n    #\n    # In theory, these annotations could be used to check arrays sizes\n    # at runtime, but at the moment nothing actually uses them, and\n    # the names are here primarily to enhance readibility and allow\n    # for some stronger type checking if numpy has stronger typing\n    # in the future\n    FloatArray = np.ndarray[int, np.dtype[np.float64]]\n    Vect2 = Annotated[FloatArray, Literal[2]]\n    Vect3 = Annotated[FloatArray, Literal[3]]\n    Vect4 = Annotated[FloatArray, Literal[4]]\n    VectN = Annotated[FloatArray, Literal[\"N\"]]\n    Matrix3x3 = Annotated[FloatArray, Literal[3, 3]]\n    VectArray = Annotated[FloatArray, Literal[\"N\", 1]]\n    Vect2Array = Annotated[FloatArray, Literal[\"N\", 2]]\n    Vect3Array = Annotated[FloatArray, Literal[\"N\", 3]]\n    Vect4Array = Annotated[FloatArray, Literal[\"N\", 4]]\n    VectNArray = Annotated[FloatArray, Literal[\"N\", \"M\"]]\n"
  },
  {
    "path": "manimlib/utils/__init__.py",
    "content": ""
  },
  {
    "path": "manimlib/utils/bezier.py",
    "content": "from __future__ import annotations\n\nimport numpy as np\nfrom scipy import linalg\nfrom fontTools.cu2qu.cu2qu import curve_to_quadratic\n\nfrom manimlib.logger import log\nfrom manimlib.utils.simple_functions import choose\nfrom manimlib.utils.space_ops import cross2d\nfrom manimlib.utils.space_ops import cross\nfrom manimlib.utils.space_ops import find_intersection\nfrom manimlib.utils.space_ops import midpoint\nfrom manimlib.utils.space_ops import get_norm\nfrom manimlib.utils.space_ops import z_to_vector\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable, Sequence, TypeVar, Tuple\n    from manimlib.typing import VectN, FloatArray, VectNArray, Vect3Array\n\n    Scalable = TypeVar(\"Scalable\", float, FloatArray)\n\n\nCLOSED_THRESHOLD = 0.001\n\n\ndef bezier(\n    points: Sequence[float | FloatArray] | VectNArray\n) -> Callable[[float], float | FloatArray]:\n    if len(points) == 0:\n        raise Exception(\"bezier cannot be calld on an empty list\")\n\n    n = len(points) - 1\n\n    def result(t: float) -> float | FloatArray:\n        return sum(\n            ((1 - t)**(n - k)) * (t**k) * choose(n, k) * point\n            for k, point in enumerate(points)\n        )\n\n    return result\n\n\ndef partial_bezier_points(\n    points: Sequence[Scalable],\n    a: float,\n    b: float\n) -> list[Scalable]:\n    \"\"\"\n    Given an list of points which define\n    a bezier curve, and two numbers 0<=a<b<=1,\n    return an list of the same size, which\n    describes the portion of the original bezier\n    curve on the interval [a, b].\n\n    This algorithm is pretty nifty, and pretty dense.\n    \"\"\"\n    if a == 1:\n        return [points[-1]] * len(points)\n\n    a_to_1 = [\n        bezier(points[i:])(a)\n        for i in range(len(points))\n    ]\n    end_prop = (b - a) / (1. - a)\n    return [\n        bezier(a_to_1[:i + 1])(end_prop)\n        for i in range(len(points))\n    ]\n\n\n# Shortened version of partial_bezier_points just for quadratics,\n# since this is called a fair amount\ndef partial_quadratic_bezier_points(\n    points: Sequence[VectN] | VectNArray,\n    a: float,\n    b: float\n) -> list[VectN]:\n    if a == 1:\n        return 3 * [points[-1]]\n\n    def curve(t):\n        return points[0] * (1 - t) * (1 - t) + 2 * points[1] * t * (1 - t) + points[2] * t * t\n    # bezier(points)\n    h0 = curve(a) if a > 0 else points[0]\n    h2 = curve(b) if b < 1 else points[2]\n    h1_prime = (1 - a) * points[1] + a * points[2]\n    end_prop = (b - a) / (1. - a)\n    h1 = (1 - end_prop) * h0 + end_prop * h1_prime\n    return [h0, h1, h2]\n\n\n# Linear interpolation variants\n\n\ndef interpolate(start: Scalable, end: Scalable, alpha: float | VectN) -> Scalable:\n    try:\n        return (1 - alpha) * start + alpha * end\n    except TypeError:\n        log.debug(f\"`start` parameter with type `{type(start)}` and dtype `{start.dtype}`\")\n        log.debug(f\"`end` parameter with type `{type(end)}` and dtype `{end.dtype}`\")\n        log.debug(f\"`alpha` parameter with value `{alpha}`\")\n        import sys\n        sys.exit(2)\n\n\ndef outer_interpolate(\n    start: Scalable,\n    end: Scalable,\n    alpha: Scalable,\n) -> np.ndarray:\n    result = np.outer(1 - alpha, start) + np.outer(alpha, end)\n    return result.reshape((*np.shape(alpha), *np.shape(start)))\n\n\ndef set_array_by_interpolation(\n    arr: np.ndarray,\n    arr1: np.ndarray,\n    arr2: np.ndarray,\n    alpha: float,\n    interp_func: Callable[[np.ndarray, np.ndarray, float], np.ndarray] = interpolate\n) -> np.ndarray:\n    arr[:] = interp_func(arr1, arr2, alpha)\n    return arr\n\n\ndef integer_interpolate(\n    start: int,\n    end: int,\n    alpha: float\n) -> tuple[int, float]:\n    \"\"\"\n    alpha is a float between 0 and 1.  This returns\n    an integer between start and end (inclusive) representing\n    appropriate interpolation between them, along with a\n    \"residue\" representing a new proportion between the\n    returned integer and the next one of the\n    list.\n\n    For example, if start=0, end=10, alpha=0.46, This\n    would return (4, 0.6).\n    \"\"\"\n    if alpha >= 1:\n        return (end - 1, 1.0)\n    if alpha <= 0:\n        return (start, 0)\n    value = int(interpolate(start, end, alpha))\n    residue = ((end - start) * alpha) % 1\n    return (value, residue)\n\n\ndef mid(start: Scalable, end: Scalable) -> Scalable:\n    return (start + end) / 2.0\n\n\ndef inverse_interpolate(start: Scalable, end: Scalable, value: Scalable) -> np.ndarray:\n    return np.true_divide(value - start, end - start)\n\n\ndef match_interpolate(\n    new_start: Scalable,\n    new_end: Scalable,\n    old_start: Scalable,\n    old_end: Scalable,\n    old_value: Scalable\n) -> Scalable:\n    return interpolate(\n        new_start, new_end,\n        inverse_interpolate(old_start, old_end, old_value)\n    )\n\n\ndef quadratic_bezier_points_for_arc(angle: float, n_components: int = 8):\n    n_points = 2 * n_components + 1\n    angles = np.linspace(0, angle, n_points)\n    points = np.array([np.cos(angles), np.sin(angles), np.zeros(n_points)]).T\n    # Adjust handles\n    theta = angle / n_components\n    points[1::2] /= np.cos(theta / 2)\n    return points\n\n\ndef approx_smooth_quadratic_bezier_handles(\n    points: FloatArray\n) -> FloatArray:\n    \"\"\"\n    Figuring out which bezier curves most smoothly connect a sequence of points.\n\n    Given three successive points, P0, P1 and P2, you can compute that by defining\n    h = (1/4) P0 + P1 - (1/4)P2, the bezier curve defined by (P0, h, P1) will pass\n    through the point P2.\n\n    So for a given set of four successive points, P0, P1, P2, P3, if we want to add\n    a handle point h between P1 and P2 so that the quadratic bezier (P1, h, P2) is\n    part of a smooth curve passing through all four points, we calculate one solution\n    for h that would produce a parbola passing through P3, call it smooth_to_right, and\n    another that would produce a parabola passing through P0, call it smooth_to_left,\n    and use the midpoint between the two.\n    \"\"\"\n    if len(points) == 1:\n        return points[0]\n    elif len(points) == 2:\n        return midpoint(*points)\n    smooth_to_right, smooth_to_left = [\n        0.25 * ps[0:-2] + ps[1:-1] - 0.25 * ps[2:]\n        for ps in (points, points[::-1])\n    ]\n    if np.isclose(points[0], points[-1]).all():\n        last_str = 0.25 * points[-2] + points[-1] - 0.25 * points[1]\n        last_stl = 0.25 * points[1] + points[0] - 0.25 * points[-2]\n    else:\n        last_str = smooth_to_left[0]\n        last_stl = smooth_to_right[0]\n    handles = 0.5 * np.vstack([smooth_to_right, [last_str]])\n    handles += 0.5 * np.vstack([last_stl, smooth_to_left[::-1]])\n    return handles\n\n\ndef smooth_quadratic_path(anchors: Vect3Array) -> Vect3Array:\n    \"\"\"\n    Returns a path defining a smooth quadratic bezier spline\n    through anchors.\n    \"\"\"\n    if len(anchors) < 2:\n        return anchors\n    elif len(anchors) == 2:\n        return np.array([anchors[0], anchors.mean(0), anchors[1]])\n\n    is_flat = (anchors[:, 2] == 0).all()\n    if not is_flat:\n        normal = cross(anchors[2] - anchors[1], anchors[1] - anchors[0])\n        rot = z_to_vector(normal)\n        anchors = np.dot(anchors, rot)\n        shift = anchors[0, 2]\n        anchors[:, 2] -= shift\n    h1s, h2s = get_smooth_cubic_bezier_handle_points(anchors)\n    quads = [anchors[0, :2]]\n    for cub_bs in zip(anchors[:-1], h1s, h2s, anchors[1:]):\n        # Try to use fontTools curve_to_quadratic\n        new_quads = curve_to_quadratic(\n            [b[:2] for b in cub_bs],\n            max_err=0.1 * get_norm(cub_bs[3] - cub_bs[0])\n        )\n        # Otherwise fall back on home baked solution\n        if new_quads is None or len(new_quads) % 2 == 0:\n            new_quads = get_quadratic_approximation_of_cubic(*cub_bs)[:, :2]\n        quads.extend(new_quads[1:])\n    new_path = np.zeros((len(quads), 3))\n    new_path[:, :2] = quads\n    if not is_flat:\n        new_path[:, 2] += shift\n        new_path = np.dot(new_path, rot.T)\n    return new_path\n\n\ndef get_smooth_cubic_bezier_handle_points(\n    points: Sequence[VectN] | VectNArray\n) -> tuple[FloatArray, FloatArray]:\n    points = np.array(points)\n    num_handles = len(points) - 1\n    dim = points.shape[1]\n    if num_handles < 1:\n        return np.zeros((0, dim)), np.zeros((0, dim))\n    # Must solve 2*num_handles equations to get the handles.\n    # l and u are the number of lower an upper diagonal rows\n    # in the matrix to solve.\n    l, u = 2, 1\n    # diag is a representation of the matrix in diagonal form\n    # See https://www.particleincell.com/2012/bezier-splines/\n    # for how to arrive at these equations\n    diag = np.zeros((l + u + 1, 2 * num_handles))\n    diag[0, 1::2] = -1\n    diag[0, 2::2] = 1\n    diag[1, 0::2] = 2\n    diag[1, 1::2] = 1\n    diag[2, 1:-2:2] = -2\n    diag[3, 0:-3:2] = 1\n    # last\n    diag[2, -2] = -1\n    diag[1, -1] = 2\n    # This is the b as in Ax = b, where we are solving for x,\n    # and A is represented using diag.  However, think of entries\n    # to x and b as being points in space, not numbers\n    b = np.zeros((2 * num_handles, dim))\n    b[1::2] = 2 * points[1:]\n    b[0] = points[0]\n    b[-1] = points[-1]\n\n    def solve_func(b):\n        return linalg.solve_banded((l, u), diag, b)\n\n    use_closed_solve_function = is_closed(points)\n    if use_closed_solve_function:\n        # Get equations to relate first and last points\n        matrix = diag_to_matrix((l, u), diag)\n        # last row handles second derivative\n        matrix[-1, [0, 1, -2, -1]] = [2, -1, 1, -2]\n        # first row handles first derivative\n        matrix[0, :] = np.zeros(matrix.shape[1])\n        matrix[0, [0, -1]] = [1, 1]\n        b[0] = 2 * points[0]\n        b[-1] = np.zeros(dim)\n\n        def closed_curve_solve_func(b):\n            return linalg.solve(matrix, b)\n\n    handle_pairs = np.zeros((2 * num_handles, dim))\n    for i in range(dim):\n        if use_closed_solve_function:\n            handle_pairs[:, i] = closed_curve_solve_func(b[:, i])\n        else:\n            handle_pairs[:, i] = solve_func(b[:, i])\n    return handle_pairs[0::2], handle_pairs[1::2]\n\n\ndef diag_to_matrix(\n    l_and_u: tuple[int, int], \n    diag: np.ndarray\n) -> np.ndarray:\n    \"\"\"\n    Converts array whose rows represent diagonal\n    entries of a matrix into the matrix itself.\n    See scipy.linalg.solve_banded\n    \"\"\"\n    l, u = l_and_u\n    dim = diag.shape[1]\n    matrix = np.zeros((dim, dim))\n    for i in range(l + u + 1):\n        np.fill_diagonal(\n            matrix[max(0, i - u):, max(0, u - i):],\n            diag[i, max(0, u - i):]\n        )\n    return matrix\n\n\ndef is_closed(points: FloatArray) -> bool:\n    return np.allclose(points[0], points[-1])\n\n\n# Given 4 control points for a cubic bezier curve (or arrays of such)\n# return control points for 2 quadratics (or 2n quadratics) approximating them.\ndef get_quadratic_approximation_of_cubic(\n    a0: FloatArray,\n    h0: FloatArray,\n    h1: FloatArray,\n    a1: FloatArray\n) -> FloatArray:\n    a0 = np.array(a0, ndmin=2)\n    h0 = np.array(h0, ndmin=2)\n    h1 = np.array(h1, ndmin=2)\n    a1 = np.array(a1, ndmin=2)\n    # Tangent vectors at the start and end.\n    T0 = h0 - a0\n    T1 = a1 - h1\n\n    # Search for inflection points.  If none are found, use the\n    # midpoint as a cut point.\n    # Based on http://www.caffeineowl.com/graphics/2d/vectorial/cubic-inflexion.html\n    has_infl = np.ones(len(a0), dtype=bool)\n\n    p = h0 - a0\n    q = h1 - 2 * h0 + a0\n    r = a1 - 3 * h1 + 3 * h0 - a0\n\n    a = cross2d(q, r)\n    b = cross2d(p, r)\n    c = cross2d(p, q)\n\n    disc = b * b - 4 * a * c\n    has_infl &= (disc > 0)\n    sqrt_disc = np.sqrt(np.abs(disc))\n    settings = np.seterr(all='ignore')\n    ti_bounds = []\n    for sgn in [-1, +1]:\n        ti = (-b + sgn * sqrt_disc) / (2 * a)\n        ti[a == 0] = (-c / b)[a == 0]\n        ti[(a == 0) & (b == 0)] = 0\n        ti_bounds.append(ti)\n    ti_min, ti_max = ti_bounds\n    np.seterr(**settings)\n    ti_min_in_range = has_infl & (0 < ti_min) & (ti_min < 1)\n    ti_max_in_range = has_infl & (0 < ti_max) & (ti_max < 1)\n\n    # Choose a value of t which starts at 0.5,\n    # but is updated to one of the inflection points\n    # if they lie between 0 and 1\n\n    t_mid = 0.5 * np.ones(len(a0))\n    t_mid[ti_min_in_range] = ti_min[ti_min_in_range]\n    t_mid[ti_max_in_range] = ti_max[ti_max_in_range]\n\n    m, n = a0.shape\n    t_mid = t_mid.repeat(n).reshape((m, n))\n\n    # Compute bezier point and tangent at the chosen value of t\n    mid = bezier([a0, h0, h1, a1])(t_mid)\n    Tm = bezier([h0 - a0, h1 - h0, a1 - h1])(t_mid)\n\n    # Intersection between tangent lines at end points\n    # and tangent in the middle\n    i0 = find_intersection(a0, T0, mid, Tm)\n    i1 = find_intersection(a1, T1, mid, Tm)\n\n    m, n = np.shape(a0)\n    result = np.zeros((5 * m, n))\n    result[0::5] = a0\n    result[1::5] = i0\n    result[2::5] = mid\n    result[3::5] = i1\n    result[4::5] = a1\n    return result\n\n\ndef get_smooth_quadratic_bezier_path_through(\n    points: Sequence[VectN]\n) -> np.ndarray:\n    # TODO\n    h0, h1 = get_smooth_cubic_bezier_handle_points(points)\n    a0 = points[:-1]\n    a1 = points[1:]\n    return get_quadratic_approximation_of_cubic(a0, h0, h1, a1)\n"
  },
  {
    "path": "manimlib/utils/cache.py",
    "content": "from __future__ import annotations\n\nimport os\nfrom diskcache import Cache\nfrom contextlib import contextmanager\nfrom functools import wraps\n\nfrom manimlib.utils.directories import get_cache_dir\nfrom manimlib.utils.simple_functions import hash_string\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    T = TypeVar('T')\n\n\nCACHE_SIZE = 1e9  # 1 Gig\n_cache = Cache(get_cache_dir(), size_limit=CACHE_SIZE)\n\n\ndef cache_on_disk(func: Callable[..., T]) -> Callable[..., T]:\n    @wraps(func)\n    def wrapper(*args, **kwargs):\n        key = hash_string(f\"{func.__name__}{args}{kwargs}\")\n        value = _cache.get(key)\n        if value is None:\n            value = func(*args, **kwargs)\n            _cache.set(key, value)\n        return value\n    return wrapper\n\n\ndef clear_cache():\n    _cache.clear()\n"
  },
  {
    "path": "manimlib/utils/color.py",
    "content": "from __future__ import annotations\n\nfrom colour import Color\nfrom colour import hex2rgb\nfrom colour import rgb2hex\nimport numpy as np\nimport random\nfrom matplotlib import pyplot\n\nfrom manimlib.constants import COLORMAP_3B1B\nfrom manimlib.constants import WHITE\nfrom manimlib.utils.bezier import interpolate\nfrom manimlib.utils.iterables import resize_with_interpolation\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Iterable, Sequence, Callable\n    from manimlib.typing import ManimColor, Vect3, Vect4, Vect3Array, Vect4Array, NDArray\n\n\ndef color_to_rgb(color: ManimColor) -> Vect3:\n    if isinstance(color, str):\n        return hex_to_rgb(color)\n    elif isinstance(color, Color):\n        return np.array(color.get_rgb())\n    else:\n        raise Exception(\"Invalid color type\")\n\n\ndef color_to_rgba(color: ManimColor, alpha: float = 1.0) -> Vect4:\n    return np.array([*color_to_rgb(color), alpha])\n\n\ndef rgb_to_color(rgb: Vect3 | Sequence[float]) -> Color:\n    try:\n        return Color(rgb=tuple(rgb))\n    except ValueError:\n        return Color(WHITE)\n\n\ndef rgba_to_color(rgba: Vect4) -> Color:\n    return rgb_to_color(rgba[:3])\n\n\ndef rgb_to_hex(rgb: Vect3 | Sequence[float]) -> str:\n    return rgb2hex(rgb, force_long=True).upper()\n\n\ndef hex_to_rgb(hex_code: str) -> Vect3:\n    return np.array(hex2rgb(hex_code))\n\n\ndef invert_color(color: ManimColor) -> Color:\n    return rgb_to_color(1.0 - color_to_rgb(color))\n\n\ndef color_to_int_rgb(color: ManimColor) -> np.ndarray[int, np.dtype[np.uint8]]:\n    return (255 * color_to_rgb(color)).astype('uint8')\n\n\ndef color_to_int_rgba(color: ManimColor, opacity: float = 1.0) -> np.ndarray[int, np.dtype[np.uint8]]:\n    alpha = int(255 * opacity)\n    return np.array([*color_to_int_rgb(color), alpha], dtype=np.uint8)\n\n\ndef color_to_hex(color: ManimColor) -> str:\n    return Color(color).get_hex_l().upper()\n\n\ndef hex_to_int(rgb_hex: str) -> int:\n    return int(rgb_hex[1:], 16)\n\n\ndef int_to_hex(rgb_int: int) -> str:\n    return f\"#{rgb_int:06x}\".upper()\n\n\ndef color_gradient(\n    reference_colors: Iterable[ManimColor],\n    length_of_output: int,\n    interp_by_hsl: bool = False,\n) -> list[Color]:\n    if length_of_output == 0:\n        return []\n    n_ref_colors = len(reference_colors)\n    alphas = np.linspace(0, (n_ref_colors - 1), length_of_output)\n    floors = alphas.astype('int')\n    alphas_mod1 = alphas % 1\n    # End edge case\n    alphas_mod1[-1] = 1\n    floors[-1] = n_ref_colors - 2\n    return [\n        interpolate_color(\n            reference_colors[i],\n            reference_colors[i + 1],\n            alpha,\n            interp_by_hsl=interp_by_hsl,\n        )\n        for i, alpha in zip(floors, alphas_mod1)\n    ]\n\n\ndef interpolate_color(\n    color1: ManimColor,\n    color2: ManimColor,\n    alpha: float,\n    interp_by_hsl: bool = False,\n) -> Color:\n    if interp_by_hsl:\n        hsl1 = np.array(Color(color1).get_hsl())\n        hsl2 = np.array(Color(color2).get_hsl())\n        return Color(hsl=interpolate(hsl1, hsl2, alpha))\n    else:\n        rgb = np.sqrt(interpolate(color_to_rgb(color1)**2, color_to_rgb(color2)**2, alpha))\n        return rgb_to_color(rgb)\n\n\ndef interpolate_color_by_hsl(\n    color1: ManimColor,\n    color2: ManimColor,\n    alpha: float\n) -> Color:\n    return interpolate_color(color1, color2, alpha, interp_by_hsl=True)\n\n\ndef average_color(*colors: ManimColor) -> Color:\n    rgbs = np.array(list(map(color_to_rgb, colors)))\n    return rgb_to_color(np.sqrt((rgbs**2).mean(0)))\n\n\ndef random_color() -> Color:\n    return Color(rgb=tuple(np.random.random(3)))\n\n\ndef random_bright_color(\n    hue_range: tuple[float, float] = (0.0, 1.0),\n    saturation_range: tuple[float, float] = (0.5, 0.8),\n    luminance_range: tuple[float, float] = (0.5, 1.0),\n) -> Color:\n    return Color(hsl=(\n        interpolate(*hue_range, random.random()),\n        interpolate(*saturation_range, random.random()),\n        interpolate(*luminance_range, random.random()),\n    ))\n\n\ndef get_colormap_from_colors(colors: Iterable[ManimColor]) -> Callable[[Sequence[float]], Vect4Array]:\n    \"\"\"\n    Returns a funciton which takes in values between 0 and 1, and returns\n    a corresponding list of rgba values\n    \"\"\"\n    rgbas = np.array([color_to_rgba(color) for color in colors])\n\n    def func(values):\n        alphas = np.clip(values, 0, 1)\n        scaled_alphas = alphas * (len(rgbas) - 1)\n        indices = scaled_alphas.astype(int)\n        next_indices = np.clip(indices + 1, 0, len(rgbas) - 1)\n        inter_alphas = scaled_alphas % 1\n        inter_alphas = inter_alphas.repeat(4).reshape((len(indices), 4))\n        result = interpolate(rgbas[indices], rgbas[next_indices], inter_alphas)\n        return result\n\n    return func\n\n\ndef get_color_map(map_name: str) -> Callable[[Sequence[float]], Vect4Array]:\n    if map_name == \"3b1b_colormap\":\n        return get_colormap_from_colors(COLORMAP_3B1B)\n    return pyplot.get_cmap(map_name)\n\n\n# Delete this?\ndef get_colormap_list(\n    map_name: str = \"viridis\",\n    n_colors: int = 9\n) -> Vect3Array:\n    \"\"\"\n    Options for map_name:\n    3b1b_colormap\n    magma\n    inferno\n    plasma\n    viridis\n    cividis\n    twilight\n    twilight_shifted\n    turbo\n    \"\"\"\n    from matplotlib.cm import cmaps_listed\n\n    if map_name == \"3b1b_colormap\":\n        rgbs = np.array([color_to_rgb(color) for color in COLORMAP_3B1B])\n    else:\n        rgbs = cmaps_listed[map_name].colors  # Make more general?\n    return resize_with_interpolation(np.array(rgbs), n_colors)\n"
  },
  {
    "path": "manimlib/utils/debug.py",
    "content": "from __future__ import annotations\n\nfrom manimlib.constants import BLACK\nfrom manimlib.logger import log\nfrom manimlib.mobject.numbers import Integer\nfrom manimlib.mobject.types.vectorized_mobject import VGroup\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from manimlib.mobject.mobject import Mobject\n\n\ndef print_family(mobject: Mobject, n_tabs: int = 0) -> None:\n    \"\"\"For debugging purposes\"\"\"\n    log.debug(\"\\t\" * n_tabs + str(mobject) + \" \" + str(id(mobject)))\n    for submob in mobject.submobjects:\n        print_family(submob, n_tabs + 1)\n\n\ndef index_labels(\n    mobject: Mobject, \n    label_height: float = 0.15\n) -> VGroup:\n    labels = VGroup()\n    for n, submob in enumerate(mobject):\n        label = Integer(n)\n        label.set_height(label_height)\n        label.move_to(submob)\n        label.set_backstroke(BLACK, 5)\n        labels.add(label)\n    return labels\n"
  },
  {
    "path": "manimlib/utils/dict_ops.py",
    "content": "import itertools as it\nimport numpy as np\n\n\ndef merge_dicts_recursively(*dicts):\n    \"\"\"\n    Creates a dict whose keyset is the union of all the\n    input dictionaries.  The value for each key is based\n    on the first dict in the list with that key.\n\n    dicts later in the list have higher priority\n\n    When values are dictionaries, it is applied recursively\n    \"\"\"\n    result = dict()\n    all_items = it.chain(*[d.items() for d in dicts])\n    for key, value in all_items:\n        if key in result and isinstance(result[key], dict) and isinstance(value, dict):\n            result[key] = merge_dicts_recursively(result[key], value)\n        else:\n            result[key] = value\n    return result\n"
  },
  {
    "path": "manimlib/utils/directories.py",
    "content": "from __future__ import annotations\n\nimport os\nimport tempfile\nimport appdirs\n\n\nfrom manimlib.config import manim_config\nfrom manimlib.config import get_manim_dir\nfrom manimlib.utils.file_ops import guarantee_existence\n\n\ndef get_directories() -> dict[str, str]:\n    return manim_config.directories\n\n\ndef get_cache_dir() -> str:\n    return get_directories()[\"cache\"] or appdirs.user_cache_dir(\"manim\")\n\n\ndef get_temp_dir() -> str:\n    return get_directories()[\"temporary_storage\"] or tempfile.gettempdir()\n\n\ndef get_downloads_dir() -> str:\n    return get_directories()[\"downloads\"] or appdirs.user_cache_dir(\"manim_downloads\")\n\n\ndef get_output_dir() -> str:\n    return guarantee_existence(get_directories()[\"output\"])\n\n\ndef get_raster_image_dir() -> str:\n    return get_directories()[\"raster_images\"]\n\n\ndef get_vector_image_dir() -> str:\n    return get_directories()[\"vector_images\"]\n\n\ndef get_three_d_model_dir() -> str:\n    return get_directories()[\"three_d_models\"]\n\n\ndef get_sound_dir() -> str:\n    return get_directories()[\"sounds\"]\n\n\ndef get_shader_dir() -> str:\n    return os.path.join(get_manim_dir(), \"manimlib\", \"shaders\")\n"
  },
  {
    "path": "manimlib/utils/family_ops.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Iterable, List, Set, Tuple\n\n    from manimlib.mobject.mobject import Mobject\n\n\ndef extract_mobject_family_members(\n    mobject_list: Iterable[Mobject],\n    exclude_pointless: bool = False\n) -> list[Mobject]:\n    return [\n        sm\n        for mob in mobject_list\n        for sm in mob.get_family()\n        if (not exclude_pointless) or sm.has_points()\n    ]\n\n\ndef recursive_mobject_remove(mobjects: List[Mobject], to_remove: Set[Mobject]) -> Tuple[List[Mobject], bool]:\n    \"\"\"\n    Takes in a list of mobjects, together with a set of mobjects to remove.\n\n    The first component of what's removed is a new list such that any mobject\n    with one of the elements from `to_remove` in its family is no longer in\n    the list, and in its place are its family members which aren't in `to_remove`\n\n    The second component is a boolean value indicating whether any removals were made\n    \"\"\"\n    result = []\n    found_in_list = False\n    for mob in mobjects:\n        if mob in to_remove:\n            found_in_list = True\n            continue\n        # Recursive call\n        sub_list, found_in_submobjects = recursive_mobject_remove(\n            mob.submobjects, to_remove\n        )\n        if found_in_submobjects:\n            result.extend(sub_list)\n            found_in_list = True\n        else:\n            result.append(mob)\n    return result, found_in_list"
  },
  {
    "path": "manimlib/utils/file_ops.py",
    "content": "from __future__ import annotations\n\nimport os\nfrom pathlib import Path\nimport hashlib\n\nimport numpy as np\nimport validators\nimport urllib.request\n\nimport manimlib.utils.directories\nfrom manimlib.utils.simple_functions import hash_string\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Iterable\n\n\ndef guarantee_existence(path: str | Path) -> Path:\n    path = Path(path)\n    path.mkdir(parents=True, exist_ok=True)\n    return path.absolute()\n\n\ndef find_file(\n    file_name: str,\n    directories: Iterable[str] | None = None,\n    extensions: Iterable[str] | None = None\n) -> Path:\n    # Check if this is a file online first, and if so, download\n    # it to the configured downloads directory\n    if validators.url(file_name):\n        suffix = Path(file_name).suffix\n        file_hash = hash_string(file_name)\n        folder = manimlib.utils.directories.get_downloads_dir()\n        path = Path(folder, file_hash).with_suffix(suffix)\n\n        # ensure that the target folder exists before downloading\n        guarantee_existence(folder)\n        urllib.request.urlretrieve(file_name, path)\n        return path\n\n    # Check if what was passed in is already a valid path to a file\n    if os.path.exists(file_name):\n        return Path(file_name)\n\n    # Otherwise look in local file system\n    directories = directories or [\"\"]\n    extensions = extensions or [\"\"]\n    possible_paths = (\n        Path(directory, file_name + extension)\n        for directory in directories\n        for extension in extensions\n    )\n    for path in possible_paths:\n        if path.exists():\n            return path\n    raise IOError(f\"{file_name} not Found\")\n"
  },
  {
    "path": "manimlib/utils/images.py",
    "content": "from __future__ import annotations\n\nimport numpy as np\nfrom PIL import Image\n\nfrom manimlib.utils.directories import get_raster_image_dir\nfrom manimlib.utils.directories import get_vector_image_dir\nfrom manimlib.utils.directories import get_three_d_model_dir\nfrom manimlib.utils.file_ops import find_file\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Iterable\n\n\ndef get_full_raster_image_path(image_file_name: str) -> str:\n    return find_file(\n        image_file_name,\n        directories=[get_raster_image_dir()],\n        extensions=[\".jpg\", \".jpeg\", \".png\", \".gif\", \"\"]\n    )\n\n\ndef get_full_vector_image_path(image_file_name: str) -> str:\n    return find_file(\n        image_file_name,\n        directories=[get_vector_image_dir()],\n        extensions=[\".svg\", \".xdv\", \"\"],\n    )\n\n\ndef get_full_three_d_model_path(model_file_name: str) -> str:\n    return find_file(\n        model_file_name,\n        directories=[get_three_d_model_dir()],\n        extensions=[\".obj\", \"\"],\n    )\n\n\ndef invert_image(image: Iterable) -> Image.Image:\n    arr = np.array(image)\n    arr = (255 * np.ones(arr.shape)).astype(arr.dtype) - arr\n    return Image.fromarray(arr)\n"
  },
  {
    "path": "manimlib/utils/iterables.py",
    "content": "from __future__ import annotations\n\nfrom colour import Color\n\nimport numpy as np\nimport random\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable, Iterable, Sequence, TypeVar\n\n    T = TypeVar(\"T\")\n    S = TypeVar(\"S\")\n\n\ndef remove_list_redundancies(lst: Sequence[T]) -> list[T]:\n    \"\"\"\n    Remove duplicate elements while preserving order.\n    Keeps the last occurrence of each element\n    \"\"\"\n    return list(reversed(dict.fromkeys(reversed(lst))))\n\n\ndef list_update(l1: Iterable[T], l2: Iterable[T]) -> list[T]:\n    \"\"\"\n    Used instead of list(set(l1).update(l2)) to maintain order,\n    making sure duplicates are removed from l1, not l2.\n    \"\"\"\n    return remove_list_redundancies([*l1, *l2])\n\n\ndef list_difference_update(l1: Iterable[T], l2: Iterable[T]) -> list[T]:\n    return [e for e in l1 if e not in l2]\n\n\ndef adjacent_n_tuples(objects: Sequence[T], n: int) -> zip[tuple[T, ...]]:\n    return zip(*[\n        [*objects[k:], *objects[:k]]\n        for k in range(n)\n    ])\n\n\ndef adjacent_pairs(objects: Sequence[T]) -> zip[tuple[T, T]]:\n    return adjacent_n_tuples(objects, 2)\n\n\ndef batch_by_property(\n    items: Iterable[T],\n    property_func: Callable[[T], S]\n) -> list[tuple[T, S]]:\n    \"\"\"\n    Takes in a list, and returns a list of tuples, (batch, prop)\n    such that all items in a batch have the same output when\n    put into property_func, and such that chaining all these\n    batches together would give the original list (i.e. order is\n    preserved)\n    \"\"\"\n    batch_prop_pairs = []\n    curr_batch = []\n    curr_prop = None\n    for item in items:\n        prop = property_func(item)\n        if prop != curr_prop:\n            # Add current batch\n            if len(curr_batch) > 0:\n                batch_prop_pairs.append((curr_batch, curr_prop))\n            # Redefine curr\n            curr_prop = prop\n            curr_batch = [item]\n        else:\n            curr_batch.append(item)\n    if len(curr_batch) > 0:\n        batch_prop_pairs.append((curr_batch, curr_prop))\n    return batch_prop_pairs\n\n\ndef listify(obj: object) -> list:\n    if isinstance(obj, str):\n        return [obj]\n    try:\n        return list(obj)\n    except TypeError:\n        return [obj]\n\n\ndef shuffled(iterable: Iterable) -> list:\n    as_list = list(iterable)\n    random.shuffle(as_list)\n    return as_list\n\n\ndef resize_array(nparray: np.ndarray, length: int) -> np.ndarray:\n    if len(nparray) == length:\n        return nparray\n    return np.resize(nparray, (length, *nparray.shape[1:]))\n\n\ndef resize_preserving_order(nparray: np.ndarray, length: int) -> np.ndarray:\n    if len(nparray) == 0:\n        return np.resize(nparray, length)\n    if len(nparray) == length:\n        return nparray\n    indices = np.arange(length) * len(nparray) // length\n    return nparray[indices]\n\n\ndef resize_with_interpolation(nparray: np.ndarray, length: int) -> np.ndarray:\n    if len(nparray) == length:\n        return nparray\n    if len(nparray) == 1 or array_is_constant(nparray):\n        return nparray[:1].repeat(length, axis=0)\n    if length == 0:\n        return np.zeros((0, *nparray.shape[1:]))\n    cont_indices = np.linspace(0, len(nparray) - 1, length)\n    return np.array([\n        (1 - a) * nparray[lh] + a * nparray[rh]\n        for ci in cont_indices\n        for lh, rh, a in [(int(ci), int(np.ceil(ci)), ci % 1)]\n    ])\n\n\ndef make_even(\n    iterable_1: Sequence[T],\n    iterable_2: Sequence[S]\n) -> tuple[Sequence[T], Sequence[S]]:\n    len1 = len(iterable_1)\n    len2 = len(iterable_2)\n    if len1 == len2:\n        return iterable_1, iterable_2\n    new_len = max(len1, len2)\n    return (\n        [iterable_1[(n * len1) // new_len] for n in range(new_len)],\n        [iterable_2[(n * len2) // new_len] for n in range(new_len)]\n    )\n\n\ndef arrays_match(arr1: np.ndarray, arr2: np.ndarray) -> bool:\n    return arr1.shape == arr2.shape and (arr1 == arr2).all()\n\n\ndef array_is_constant(arr: np.ndarray) -> bool:\n    return len(arr) > 0 and (arr == arr[0]).all()\n\n\ndef cartesian_product(*arrays: np.ndarray):\n    \"\"\"\n    Copied from https://stackoverflow.com/a/11146645\n    \"\"\"\n    la = len(arrays)\n    dtype = np.result_type(*arrays)\n    arr = np.empty([len(a) for a in arrays] + [la], dtype=dtype)\n    for i, a in enumerate(np.ix_(*arrays)):\n        arr[..., i] = a\n    return arr.reshape(-1, la)\n\n\ndef hash_obj(obj: object) -> int:\n    if isinstance(obj, dict):\n        return hash(tuple(sorted([\n            (hash_obj(k), hash_obj(v)) for k, v in obj.items()\n        ])))\n\n    if isinstance(obj, set):\n        return hash(tuple(sorted(hash_obj(e) for e in obj)))\n\n    if isinstance(obj, (tuple, list)):\n        return hash(tuple(hash_obj(e) for e in obj))\n\n    if isinstance(obj, Color):\n        return hash(obj.get_rgb())\n\n    return hash(obj)\n"
  },
  {
    "path": "manimlib/utils/paths.py",
    "content": "from __future__ import annotations\n\nimport math\n\nimport numpy as np\n\nfrom manimlib.constants import OUT\nfrom manimlib.utils.bezier import interpolate\nfrom manimlib.utils.space_ops import get_norm\nfrom manimlib.utils.space_ops import rotation_matrix_transpose\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable\n    from manimlib.typing import Vect3, Vect3Array\n\n\nSTRAIGHT_PATH_THRESHOLD = 0.01\n\n\ndef straight_path(\n    start_points: np.ndarray,\n    end_points: np.ndarray,\n    alpha: float\n) -> np.ndarray:\n    \"\"\"\n    Same function as interpolate, but renamed to reflect\n    intent of being used to determine how a set of points move\n    to another set.  For instance, it should be a specific case\n    of path_along_arc\n    \"\"\"\n    return interpolate(start_points, end_points, alpha)\n\n\ndef path_along_arc(\n    arc_angle: float | Tuple[float, float] | np.ndarray,\n    axis: Vect3 = OUT\n) -> Callable[[Vect3Array, Vect3Array, float], Vect3Array]:\n    \"\"\"\n    arc_angle can be a single angle, or a pair of angles, in which case\n    the range of all angles between that pair will be used.\n\n    If vect is vector from start to end, [vect[:,1], -vect[:,0]] is\n    perpendicular to vect in the left direction.\n    \"\"\"\n    if isinstance(arc_angle, float | int) and abs(arc_angle) < STRAIGHT_PATH_THRESHOLD:\n        return straight_path\n    if get_norm(axis) == 0:\n        axis = OUT\n    unit_axis = axis / get_norm(axis)\n\n    def path(start_points, end_points, alpha):\n        if isinstance(arc_angle, float | int):\n            theta = arc_angle\n        else:\n            if isinstance(arc_angle, np.ndarray) and len(arc_angle) == len(start_points):\n                theta_range = arc_angle\n            else:\n                theta_range = np.linspace(arc_angle[0], arc_angle[-1], len(start_points))\n            # Avoid zero, mildly hacky\n            theta_range[np.abs(theta_range) < STRAIGHT_PATH_THRESHOLD] = STRAIGHT_PATH_THRESHOLD\n            # Get shape to match\n            theta = theta_range[:, np.newaxis] * np.ones(start_points.shape[1])\n        start_to_end = end_points - start_points\n\n        with np.errstate(divide='ignore', invalid='ignore'):\n            adjustments = np.nan_to_num(np.cross(unit_axis, start_to_end / 2.0) / np.tan(theta / 2))\n            arc_centers = start_points + 0.5 * start_to_end + adjustments\n\n        c_to_start = start_points - arc_centers\n        c_to_perp = np.cross(unit_axis, c_to_start)\n        return arc_centers + np.cos(alpha * theta) * c_to_start + np.sin(alpha * theta) * c_to_perp\n\n    return path\n\n\ndef clockwise_path() -> Callable[[Vect3Array, Vect3Array, float], Vect3Array]:\n    return path_along_arc(-np.pi)\n\n\ndef counterclockwise_path() -> Callable[[Vect3Array, Vect3Array, float], Vect3Array]:\n    return path_along_arc(np.pi)\n"
  },
  {
    "path": "manimlib/utils/rate_functions.py",
    "content": "from __future__ import annotations\n\nimport numpy as np\n\nfrom manimlib.utils.bezier import bezier\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable\n\n\ndef linear(t: float) -> float:\n    return t\n\n\ndef smooth(t: float) -> float:\n    # Zero first and second derivatives at t=0 and t=1.\n    # Equivalent to bezier([0, 0, 0, 1, 1, 1])\n    s = 1 - t\n    return (t**3) * (10 * s * s + 5 * s * t + t * t)\n\n\ndef rush_into(t: float) -> float:\n    return 2 * smooth(0.5 * t)\n\n\ndef rush_from(t: float) -> float:\n    return 2 * smooth(0.5 * (t + 1)) - 1\n\n\ndef slow_into(t: float) -> float:\n    return np.sqrt(1 - (1 - t) * (1 - t))\n\n\ndef double_smooth(t: float) -> float:\n    if t < 0.5:\n        return 0.5 * smooth(2 * t)\n    else:\n        return 0.5 * (1 + smooth(2 * t - 1))\n\n\ndef there_and_back(t: float) -> float:\n    new_t = 2 * t if t < 0.5 else 2 * (1 - t)\n    return smooth(new_t)\n\n\ndef there_and_back_with_pause(t: float, pause_ratio: float = 1. / 3) -> float:\n    a = 2. / (1. - pause_ratio)\n    if t < 0.5 - pause_ratio / 2:\n        return smooth(a * t)\n    elif t < 0.5 + pause_ratio / 2:\n        return 1\n    else:\n        return smooth(a - a * t)\n\n\ndef running_start(t: float, pull_factor: float = -0.5) -> float:\n    return bezier([0, 0, pull_factor, pull_factor, 1, 1, 1])(t)\n\n\ndef overshoot(t: float, pull_factor: float = 1.5) -> float:\n    return bezier([0, 0, pull_factor, pull_factor, 1, 1])(t)\n\n\ndef not_quite_there(\n    func: Callable[[float], float] = smooth,\n    proportion: float = 0.7\n) -> Callable[[float], float]:\n    def result(t):\n        return proportion * func(t)\n    return result\n\n\ndef wiggle(t: float, wiggles: float = 2) -> float:\n    return there_and_back(t) * np.sin(wiggles * np.pi * t)\n\n\ndef squish_rate_func(\n    func: Callable[[float], float],\n    a: float = 0.4,\n    b: float = 0.6\n) -> Callable[[float], float]:\n    def result(t):\n        if a == b:\n            return a\n        elif t < a:\n            return func(0)\n        elif t > b:\n            return func(1)\n        else:\n            return func((t - a) / (b - a))\n\n    return result\n\n# Stylistically, should this take parameters (with default values)?\n# Ultimately, the functionality is entirely subsumed by squish_rate_func,\n# but it may be useful to have a nice name for with nice default params for\n# \"lingering\", different from squish_rate_func's default params\n\n\ndef lingering(t: float) -> float:\n    return squish_rate_func(lambda t: t, 0, 0.8)(t)\n\n\ndef exponential_decay(t: float, half_life: float = 0.1) -> float:\n    # The half-life should be rather small to minimize\n    # the cut-off error at the end\n    return 1 - np.exp(-t / half_life)\n"
  },
  {
    "path": "manimlib/utils/shaders.py",
    "content": "from __future__ import annotations\n\nimport os\nimport re\nfrom functools import lru_cache\nimport moderngl\nfrom PIL import Image\nimport numpy as np\n\nfrom manimlib.utils.directories import get_shader_dir\nfrom manimlib.utils.file_ops import find_file\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Sequence, Optional\n\n\n# Global maps to reflect uniform status\nPROGRAM_UNIFORM_MIRRORS: dict[int, dict[str, float | tuple]] = dict()\n\n\n@lru_cache()\ndef image_path_to_texture(path: str, ctx: moderngl.Context) -> moderngl.Texture:\n    im = Image.open(path).convert(\"RGBA\")\n    return ctx.texture(\n        size=im.size,\n        components=len(im.getbands()),\n        data=im.tobytes(),\n    )\n\n\n@lru_cache()\ndef get_shader_program(\n        ctx: moderngl.context.Context,\n        vertex_shader: str,\n        fragment_shader: Optional[str] = None,\n        geometry_shader: Optional[str] = None,\n) -> moderngl.Program:\n    return ctx.program(\n        vertex_shader=vertex_shader,\n        fragment_shader=fragment_shader,\n        geometry_shader=geometry_shader,\n    )\n\n\ndef set_program_uniform(\n    program: moderngl.Program,\n    name: str,\n    value: float | tuple | np.ndarray\n) -> bool:\n    \"\"\"\n    Sets a program uniform, and also keeps track of a dictionary\n    of previously set uniforms for that program so that it\n    doesn't needlessly reset it, requiring an exchange with gpu\n    memory, if it sees the same value again.\n\n    Returns True if changed the program, False if it left it as is.\n    \"\"\"\n\n    pid = id(program)\n    if pid not in PROGRAM_UNIFORM_MIRRORS:\n        PROGRAM_UNIFORM_MIRRORS[pid] = dict()\n    uniform_mirror = PROGRAM_UNIFORM_MIRRORS[pid]\n\n    if type(value) is np.ndarray and value.ndim > 0:\n        value = tuple(value.flatten())\n    if uniform_mirror.get(name, None) == value:\n        return False\n\n    try:\n        program[name].value = value\n    except KeyError:\n        return False\n    uniform_mirror[name] = value\n    return True\n\n\n@lru_cache()\ndef get_shader_code_from_file(filename: str) -> str | None:\n    if not filename:\n        return None\n\n    try:\n        filepath = find_file(\n            filename,\n            directories=[get_shader_dir(), \"/\"],\n            extensions=[],\n        )\n    except IOError:\n        return None\n\n    with open(filepath, \"r\") as f:\n        result = f.read()\n\n    # To share functionality between shaders, some functions are read in\n    # from other files an inserted into the relevant strings before\n    # passing to ctx.program for compiling\n    # Replace \"#INSERT \" lines with relevant code\n    insertions = re.findall(r\"^#INSERT .*\\.glsl$\", result, flags=re.MULTILINE)\n    for line in insertions:\n        inserted_code = get_shader_code_from_file(\n            os.path.join(\"inserts\", line.replace(\"#INSERT \", \"\"))\n        )\n        result = result.replace(line, inserted_code)\n    return result\n\n\ndef get_colormap_code(rgb_list: Sequence[float]) -> str:\n    data = \",\".join(\n        \"vec3({}, {}, {})\".format(*rgb)\n        for rgb in rgb_list\n    )\n    return f\"vec3[{len(rgb_list)}]({data})\"\n"
  },
  {
    "path": "manimlib/utils/simple_functions.py",
    "content": "from __future__ import annotations\n\nfrom functools import lru_cache\nimport hashlib\nimport inspect\nimport math\n\nimport numpy as np\n\nfrom typing import TYPE_CHECKING\nif TYPE_CHECKING:\n    from typing import Callable, TypeVar, Iterable\n    from manimlib.typing import FloatArray\n\n    Scalable = TypeVar(\"Scalable\", float, FloatArray)\n\n\n\ndef sigmoid(x: float | FloatArray):\n    return 1.0 / (1 + np.exp(-x))\n\n\n@lru_cache(maxsize=10)\ndef choose(n: int, k: int) -> int:\n    return math.comb(n, k)\n\n\ndef gen_choose(n: int, r: int) -> int:\n    return int(np.prod(range(n, n - r, -1)) / math.factorial(r))\n\n\ndef get_num_args(function: Callable) -> int:\n    return function.__code__.co_argcount\n\n\ndef get_parameters(function: Callable) -> Iterable[str]:\n    return inspect.signature(function).parameters.keys()\n\n\ndef clip(a: float, min_a: float, max_a: float) -> float:\n    if a < min_a:\n        return min_a\n    elif a > max_a:\n        return max_a\n    return a\n\n\ndef arr_clip(arr: np.ndarray, min_a: float, max_a: float) -> np.ndarray:\n    arr[arr < min_a] = min_a\n    arr[arr > max_a] = max_a\n    return arr\n\n\ndef fdiv(a: Scalable, b: Scalable, zero_over_zero_value: Scalable | None = None) -> Scalable:\n    \"\"\"\n    Less heavyweight name for np.true_divide, enabling\n    default behavior for 0/0\n    \"\"\"\n    if zero_over_zero_value is not None:\n        out = np.full_like(a, zero_over_zero_value)\n        where = np.logical_or(a != 0, b != 0)\n    else:\n        out = None\n        where = True\n\n    return np.true_divide(a, b, out=out, where=where)\n\n\ndef binary_search(\n    function: Callable[[float], float],\n    target: float,\n    lower_bound: float,\n    upper_bound: float,\n    tolerance:float = 1e-4\n) -> float | None:\n    lh = lower_bound\n    rh = upper_bound\n    mh = (lh + rh) / 2\n    while abs(rh - lh) > tolerance:\n        lx, mx, rx = [function(h) for h in (lh, mh, rh)]\n        if lx == target:\n            return lx\n        if rx == target:\n            return rx\n\n        if lx <= target and rx >= target:\n            if mx > target:\n                rh = mh\n            else:\n                lh = mh\n        elif lx > target and rx < target:\n            lh, rh = rh, lh\n        else:\n            return None\n        mh = (lh + rh) / 2\n    return mh\n\n\ndef hash_string(string: str, n_bytes=16) -> str:\n    hasher = hashlib.sha256(string.encode())\n    return hasher.hexdigest()[:n_bytes]\n"
  },
  {
    "path": "manimlib/utils/sounds.py",
    "content": "from __future__ import annotations\n\nimport subprocess\nimport threading\nimport platform\n\nfrom manimlib.utils.directories import get_sound_dir\nfrom manimlib.utils.file_ops import find_file\n\n\ndef get_full_sound_file_path(sound_file_name: str) -> str:\n    return find_file(\n        sound_file_name,\n        directories=[get_sound_dir()],\n        extensions=[\".wav\", \".mp3\", \"\"]\n    )\n\n\ndef play_sound(sound_file):\n    \"\"\"Play a sound file using the system's audio player\"\"\"\n    full_path = get_full_sound_file_path(sound_file)\n    system = platform.system()\n\n    if system == \"Windows\":\n        # Windows\n        subprocess.Popen(\n            [\"powershell\", \"-c\", f\"(New-Object Media.SoundPlayer '{full_path}').PlaySync()\"],\n            shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL\n    )\n    elif system == \"Darwin\":\n        # macOS\n        subprocess.Popen(\n            [\"afplay\", full_path],\n            stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL\n        )\n    else:\n        subprocess.Popen(\n            [\"aplay\", full_path],\n            stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL\n        )\n"
  },
  {
    "path": "manimlib/utils/space_ops.py",
    "content": "from __future__ import annotations\n\nfrom functools import reduce\nimport math\nimport operator as op\nimport platform\n\nfrom mapbox_earcut import triangulate_float32 as earcut\nimport numpy as np\nfrom scipy.spatial.transform import Rotation\nfrom tqdm.auto import tqdm as ProgressDisplay\n\nfrom manimlib.constants import DOWN, OUT, RIGHT, UP\nfrom manimlib.constants import PI, TAU\nfrom manimlib.utils.iterables import adjacent_pairs\nfrom manimlib.utils.simple_functions import clip\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable, Sequence, List, Tuple\n    from manimlib.typing import Vect2, Vect3, Vect4, VectN, Matrix3x3, Vect3Array, Vect2Array\n\n\ndef cross(\n    v1: Vect3 | List[float],\n    v2: Vect3 | List[float],\n    out: np.ndarray | None = None\n) -> Vect3 | Vect3Array:\n    is2d = isinstance(v1, np.ndarray) and len(v1.shape) == 2\n    if is2d:\n        x1, y1, z1 = v1[:, 0], v1[:, 1], v1[:, 2]\n        x2, y2, z2 = v2[:, 0], v2[:, 1], v2[:, 2]\n    else:\n        x1, y1, z1 = v1\n        x2, y2, z2 = v2\n    if out is None:\n        out = np.empty(np.shape(v1))\n    out.T[:] = [\n        y1 * z2 - z1 * y2,\n        z1 * x2 - x1 * z2,\n        x1 * y2 - y1 * x2,\n    ]\n    return out\n\n\ndef get_norm(vect: VectN | List[float]) -> float:\n    return sum((x**2 for x in vect))**0.5\n\n\ndef get_dist(vect1: VectN, vect2: VectN):\n    return get_norm(vect2 - vect1)\n\n\ndef normalize(\n    vect: VectN | List[float],\n    fall_back: VectN | List[float] | None = None\n) -> VectN:\n    norm = get_norm(vect)\n    if norm > 0:\n        return np.array(vect) / norm\n    elif fall_back is not None:\n        return np.array(fall_back)\n    else:\n        return np.zeros(len(vect))\n\n\ndef poly_line_length(points):\n    \"\"\"\n    Return the sum of the lengths between adjacent points\n    \"\"\"\n    diffs = points[1:] - points[:-1]\n    return np.sqrt((diffs**2).sum(1)).sum()\n\n# Operations related to rotation\n\n\ndef quaternion_mult(*quats: Vect4) -> Vect4:\n    \"\"\"\n    Inputs are treated as quaternions, where the real part is the\n    last entry, so as to follow the scipy Rotation conventions.\n    \"\"\"\n    if len(quats) == 0:\n        return np.array([0, 0, 0, 1])\n    result = np.array(quats[0])\n    for next_quat in quats[1:]:\n        x1, y1, z1, w1 = result\n        x2, y2, z2, w2 = next_quat\n        result[:] = [\n            w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2,\n            w1 * y2 + y1 * w2 + z1 * x2 - x1 * z2,\n            w1 * z2 + z1 * w2 + x1 * y2 - y1 * x2,\n            w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2,\n        ]\n    return result\n\n\ndef quaternion_from_angle_axis(\n    angle: float,\n    axis: Vect3,\n) -> Vect4:\n    return Rotation.from_rotvec(angle * normalize(axis)).as_quat()\n\n\ndef angle_axis_from_quaternion(quat: Vect4) -> Tuple[float, Vect3]:\n    rot_vec = Rotation.from_quat(quat).as_rotvec()\n    norm = get_norm(rot_vec)\n    return norm, rot_vec / norm\n\n\ndef quaternion_conjugate(quaternion: Vect4) -> Vect4:\n    result = np.array(quaternion)\n    result[:3] *= -1\n    return result\n\n\ndef rotate_vector(\n    vector: Vect3,\n    angle: float,\n    axis: Vect3 = OUT\n) -> Vect3:\n    rot = Rotation.from_rotvec(angle * normalize(axis))\n    return np.dot(vector, rot.as_matrix().T)\n\n\ndef rotate_vector_2d(vector: Vect2, angle: float) -> Vect2:\n    # Use complex numbers...because why not\n    z = complex(*vector) * np.exp(complex(0, angle))\n    return np.array([z.real, z.imag])\n\n\ndef rotation_matrix_transpose_from_quaternion(quat: Vect4) -> Matrix3x3:\n    return Rotation.from_quat(quat).as_matrix()\n\n\ndef rotation_matrix_from_quaternion(quat: Vect4) -> Matrix3x3:\n    return np.transpose(rotation_matrix_transpose_from_quaternion(quat))\n\n\ndef rotation_matrix(angle: float, axis: Vect3) -> Matrix3x3:\n    \"\"\"\n    Rotation in R^3 about a specified axis of rotation.\n    \"\"\"\n    return Rotation.from_rotvec(angle * normalize(axis)).as_matrix()\n\n\ndef rotation_matrix_transpose(angle: float, axis: Vect3) -> Matrix3x3:\n    return rotation_matrix(angle, axis).T\n\n\ndef rotation_about_z(angle: float) -> Matrix3x3:\n    cos_a = math.cos(angle)\n    sin_a = math.sin(angle)\n    return np.array([\n        [cos_a, -sin_a, 0],\n        [sin_a, cos_a, 0],\n        [0, 0, 1]\n    ])\n\n\ndef rotation_between_vectors(v1: Vect3, v2: Vect3) -> Matrix3x3:\n    atol = 1e-8\n    if get_norm(v1 - v2) < atol:\n        return np.identity(3)\n    axis = cross(v1, v2)\n    if get_norm(axis) < atol:\n        # v1 and v2 align\n        axis = cross(v1, RIGHT)\n    if get_norm(axis) < atol:\n        # v1 and v2 _and_ RIGHT all align\n        axis = cross(v1, UP)\n    return rotation_matrix(\n        angle=angle_between_vectors(v1, v2),\n        axis=axis,\n    )\n\n\ndef z_to_vector(vector: Vect3) -> Matrix3x3:\n    return rotation_between_vectors(OUT, vector)\n\n\ndef angle_of_vector(vector: Vect2 | Vect3) -> float:\n    \"\"\"\n    Returns polar coordinate theta when vector is project on xy plane\n    \"\"\"\n    return math.atan2(vector[1], vector[0])\n\n\ndef angle_between_vectors(v1: VectN, v2: VectN) -> float:\n    \"\"\"\n    Returns the angle between two 3D vectors.\n    This angle will always be btw 0 and pi\n    \"\"\"\n    n1 = get_norm(v1)\n    n2 = get_norm(v2)\n    if n1 == 0 or n2 == 0:\n        return 0\n    cos_angle = np.dot(v1, v2) / np.float64(n1 * n2)\n    return math.acos(clip(cos_angle, -1, 1))\n\n\ndef project_along_vector(point: Vect3, vector: Vect3) -> Vect3:\n    matrix = np.identity(3) - np.outer(vector, vector)\n    return np.dot(point, matrix.T)\n\n\ndef normalize_along_axis(\n    array: np.ndarray,\n    axis: int,\n) -> np.ndarray:\n    norms = np.sqrt((array * array).sum(axis))\n    norms[norms == 0] = 1\n    return array / norms[:, np.newaxis]\n\n\ndef get_unit_normal(\n    v1: Vect3,\n    v2: Vect3,\n    tol: float = 1e-6\n) -> Vect3:\n    v1 = normalize(v1)\n    v2 = normalize(v2)\n    cp = cross(v1, v2)\n    cp_norm = get_norm(cp)\n    if cp_norm < tol:\n        # Vectors align, so find a normal to them in the plane shared with the z-axis\n        new_cp = cross(cross(v1, OUT), v1)\n        new_cp_norm = get_norm(new_cp)\n        if new_cp_norm < tol:\n            return DOWN\n        return new_cp / new_cp_norm\n    return cp / cp_norm\n\n\n###\n\n\ndef thick_diagonal(dim: int, thickness: int = 2) -> np.ndarray:\n    row_indices = np.arange(dim).repeat(dim).reshape((dim, dim))\n    col_indices = np.transpose(row_indices)\n    return (np.abs(row_indices - col_indices) < thickness).astype('uint8')\n\n\ndef compass_directions(n: int = 4, start_vect: Vect3 = RIGHT) -> Vect3:\n    angle = TAU / n\n    return np.array([\n        rotate_vector(start_vect, k * angle)\n        for k in range(n)\n    ])\n\n\ndef complex_to_R3(complex_num: complex) -> Vect3:\n    return np.array((complex_num.real, complex_num.imag, 0))\n\n\ndef R3_to_complex(point: Vect3) -> complex:\n    return complex(*point[:2])\n\n\ndef complex_func_to_R3_func(complex_func: Callable[[complex], complex]) -> Callable[[Vect3], Vect3]:\n    def result(p: Vect3):\n        return complex_to_R3(complex_func(R3_to_complex(p)))\n    return result\n\n\ndef center_of_mass(points: Sequence[Vect3]) -> Vect3:\n    return np.array(points).sum(0) / len(points)\n\n\ndef midpoint(point1: VectN, point2: VectN) -> VectN:\n    return center_of_mass([point1, point2])\n\n\ndef line_intersection(\n    line1: Tuple[Vect3, Vect3],\n    line2: Tuple[Vect3, Vect3]\n) -> Vect3:\n    \"\"\"\n    return intersection point of two lines,\n    each defined with a pair of vectors determining\n    the end points\n    \"\"\"\n    x_diff = (line1[0][0] - line1[1][0], line2[0][0] - line2[1][0])\n    y_diff = (line1[0][1] - line1[1][1], line2[0][1] - line2[1][1])\n\n    def det(a, b):\n        return a[0] * b[1] - a[1] * b[0]\n\n    div = det(x_diff, y_diff)\n    if div == 0:\n        raise Exception(\"Lines do not intersect\")\n    d = (det(*line1), det(*line2))\n    x = det(d, x_diff) / div\n    y = det(d, y_diff) / div\n    return np.array([x, y, 0])\n\n\ndef find_intersection(\n    p0: Vect3 | Vect3Array,\n    v0: Vect3 | Vect3Array,\n    p1: Vect3 | Vect3Array,\n    v1: Vect3 | Vect3Array,\n    threshold: float = 1e-5,\n) -> Vect3:\n    \"\"\"\n    Return the intersection of a line passing through p0 in direction v0\n    with one passing through p1 in direction v1.  (Or array of intersections\n    from arrays of such points/directions).\n\n    For 3d values, it returns the point on the ray p0 + v0 * t closest to the\n    ray p1 + v1 * t\n    \"\"\"\n    d = len(p0.shape)\n    if d == 1:\n        is_3d = any(arr[2] for arr in (p0, v0, p1, v1))\n    else:\n        is_3d = any(z for arr in (p0, v0, p1, v1) for z in arr.T[2])\n    if not is_3d:\n        numer = np.array(cross2d(v1, p1 - p0))\n        denom = np.array(cross2d(v1, v0))\n    else:\n        cp1 = cross(v1, p1 - p0)\n        cp2 = cross(v1, v0)\n        numer = np.array((cp1 * cp1).sum(d - 1))\n        denom = np.array((cp1 * cp2).sum(d - 1))\n    denom[abs(denom) < threshold] = np.inf\n    ratio = numer / denom\n    return p0 + (ratio * v0.T).T\n\n\ndef line_intersects_path(\n    start: Vect2 | Vect3,\n    end: Vect2 | Vect3,\n    path: Vect2Array | Vect3Array,\n) -> bool:\n    \"\"\"\n    Tests whether the line (start, end) intersects\n    a polygonal path defined by its vertices\n    \"\"\"\n    n = len(path) - 1\n    p1 = np.empty((n, 2))\n    q1 = np.empty((n, 2))\n    p1[:] = start[:2]\n    q1[:] = end[:2]\n    p2 = path[:-1, :2]\n    q2 = path[1:, :2]\n\n    v1 = q1 - p1\n    v2 = q2 - p2\n\n    mis1 = cross2d(v1, p2 - p1) * cross2d(v1, q2 - p1) < 0\n    mis2 = cross2d(v2, p1 - p2) * cross2d(v2, q1 - p2) < 0\n    return bool((mis1 * mis2).any())\n\n\ndef get_closest_point_on_line(a: VectN, b: VectN, p: VectN) -> VectN:\n    \"\"\"\n        It returns point x such that\n        x is on line ab and xp is perpendicular to ab.\n        If x lies beyond ab line, then it returns nearest edge(a or b).\n    \"\"\"\n    # x = b + t*(a-b) = t*a + (1-t)*b\n    t = np.dot(p - b, a - b) / np.dot(a - b, a - b)\n    if t < 0:\n        t = 0\n    if t > 1:\n        t = 1\n    return ((t * a) + ((1 - t) * b))\n\n\ndef get_winding_number(points: Sequence[Vect2 | Vect3]) -> float:\n    total_angle = 0\n    for p1, p2 in adjacent_pairs(points):\n        d_angle = angle_of_vector(p2) - angle_of_vector(p1)\n        d_angle = ((d_angle + PI) % TAU) - PI\n        total_angle += d_angle\n    return total_angle / TAU\n\n\n##\n\ndef cross2d(a: Vect2 | Vect2Array, b: Vect2 | Vect2Array) -> Vect2 | Vect2Array:\n    if len(a.shape) == 2:\n        return a[:, 0] * b[:, 1] - a[:, 1] * b[:, 0]\n    else:\n        return a[0] * b[1] - b[0] * a[1]\n\n\ndef tri_area(\n    a: Vect2,\n    b: Vect2,\n    c: Vect2\n) -> float:\n    return 0.5 * abs(\n        a[0] * (b[1] - c[1]) +\n        b[0] * (c[1] - a[1]) +\n        c[0] * (a[1] - b[1])\n    )\n\n\ndef is_inside_triangle(\n    p: Vect2,\n    a: Vect2,\n    b: Vect2,\n    c: Vect2\n) -> bool:\n    \"\"\"\n    Test if point p is inside triangle abc\n    \"\"\"\n    crosses = np.array([\n        cross2d(p - a, b - p),\n        cross2d(p - b, c - p),\n        cross2d(p - c, a - p),\n    ])\n    return bool(np.all(crosses > 0) or np.all(crosses < 0))\n\n\ndef norm_squared(v: VectN | List[float]) -> float:\n    return sum(x * x for x in v)\n\n\n# TODO, fails for polygons drawn over themselves\ndef earclip_triangulation(verts: Vect3Array | Vect2Array, ring_ends: list[int]) -> list[int]:\n    \"\"\"\n    Returns a list of indices giving a triangulation\n    of a polygon, potentially with holes\n\n    - verts is a numpy array of points\n\n    - ring_ends is a list of indices indicating where\n    the ends of new paths are\n    \"\"\"\n\n    rings = [\n        list(range(e0, e1))\n        for e0, e1 in zip([0, *ring_ends], ring_ends)\n    ]\n    epsilon = 1e-6\n\n    def is_in(point, ring_id):\n        return abs(abs(get_winding_number([i - point for i in verts[rings[ring_id]]])) - 1) < epsilon\n\n    def ring_area(ring_id):\n        ring = rings[ring_id]\n        s = 0\n        for i, j in zip(ring[1:], ring):\n            s += cross2d(verts[i], verts[j])\n        return abs(s) / 2\n\n    # Points at the same position may cause problems\n    for i in rings:\n        if len(i) < 2:\n            continue\n        verts[i[0]] += (verts[i[1]] - verts[i[0]]) * epsilon\n        verts[i[-1]] += (verts[i[-2]] - verts[i[-1]]) * epsilon\n\n    # First, we should know which rings are directly contained in it for each ring\n\n    right = [max(verts[rings[i], 0]) for i in range(len(rings))]\n    left = [min(verts[rings[i], 0]) for i in range(len(rings))]\n    top = [max(verts[rings[i], 1]) for i in range(len(rings))]\n    bottom = [min(verts[rings[i], 1]) for i in range(len(rings))]\n    area = [ring_area(i) for i in range(len(rings))]\n\n    # The larger ring must be outside\n    rings_sorted = list(range(len(rings)))\n    rings_sorted.sort(key=lambda x: area[x], reverse=True)\n\n    def is_in_fast(ring_a, ring_b):\n        # Whether a is in b\n        return reduce(op.and_, (\n            left[ring_b] <= left[ring_a] <= right[ring_a] <= right[ring_b],\n            bottom[ring_b] <= bottom[ring_a] <= top[ring_a] <= top[ring_b],\n            is_in(verts[rings[ring_a][0]], ring_b)\n        ))\n\n    chilren = [[] for i in rings]\n    ringenum = ProgressDisplay(\n        enumerate(rings_sorted),\n        total=len(rings),\n        leave=False,\n        ascii=True if platform.system() == 'Windows' else None,\n        dynamic_ncols=True,\n        desc=\"SVG Triangulation\",\n        delay=3,\n    )\n    for idx, i in ringenum:\n        for j in rings_sorted[:idx][::-1]:\n            if is_in_fast(i, j):\n                chilren[j].append(i)\n                break\n\n    res = []\n\n    # Then, we can use earcut for each part\n    used = [False] * len(rings)\n    for i in rings_sorted:\n        if used[i]:\n            continue\n        v = rings[i]\n        ring_ends = [len(v)]\n        for j in chilren[i]:\n            used[j] = True\n            v += rings[j]\n            ring_ends.append(len(v))\n        res += [v[i] for i in earcut(verts[v, :2], ring_ends)]\n\n    return res\n"
  },
  {
    "path": "manimlib/utils/tex.py",
    "content": "from __future__ import annotations\n\nimport re\nfrom functools import lru_cache\n\nfrom manimlib.utils.tex_to_symbol_count import TEX_TO_SYMBOL_COUNT\n\n\n@lru_cache\ndef num_tex_symbols(tex: str) -> int:\n    tex = remove_tex_environments(tex)\n    commands_pattern = r\"\"\"\n        (?P<sqrt>\\\\sqrt\\[[0-9]+\\])|    # Special sqrt with number\n        (?P<escaped_brace>\\\\[{}])|      # Escaped braces\n        (?P<cmd>\\\\[a-zA-Z!,-/:;<>]+)    # Regular commands\n    \"\"\"\n    total = 0\n    pos = 0\n    for match in re.finditer(commands_pattern, tex, re.VERBOSE):\n        # Count normal characters up to this command\n        total += sum(1 for c in tex[pos:match.start()] if c not in \"^{} \\n\\t_$\\\\&\")\n\n        if match.group(\"sqrt\"):\n            total += len(match.group()) - 5\n        elif match.group(\"escaped_brace\"):\n            total += 1  # Count escaped brace as one symbol\n        else:\n            total += TEX_TO_SYMBOL_COUNT.get(match.group(), 1)\n        pos = match.end()\n\n    # Count remaining characters\n    total += sum(1 for c in tex[pos:] if c not in \"^{} \\n\\t_$\\\\&\")\n    return total\n\n\ndef remove_tex_environments(tex: str) -> str:\n    # Handle \\phantom{...} with any content\n    tex = re.sub(r\"\\\\phantom\\{[^}]*\\}\", \"\", tex)\n    # Handle other environment commands\n    tex = re.sub(r\"\\\\(begin|end)(\\{\\w+\\})?(\\{\\w+\\})?(\\[\\w+\\])?\", \"\", tex)\n    return tex\n"
  },
  {
    "path": "manimlib/utils/tex_file_writing.py",
    "content": "from __future__ import annotations\n\nimport os\nimport re\nimport yaml\nimport subprocess\nfrom functools import lru_cache\n\nfrom pathlib import Path\nimport tempfile\n\nfrom manimlib.utils.cache import cache_on_disk\nfrom manimlib.config import manim_config\nfrom manimlib.config import get_manim_dir\nfrom manimlib.logger import log\nfrom manimlib.utils.simple_functions import hash_string\n\n\ndef get_tex_template_config(template_name: str) -> dict[str, str]:\n    name = template_name.replace(\" \", \"_\").lower()\n    template_path = os.path.join(get_manim_dir(), \"manimlib\", \"tex_templates.yml\")\n    with open(template_path, encoding=\"utf-8\") as tex_templates_file:\n        templates_dict = yaml.safe_load(tex_templates_file)\n    if name not in templates_dict:\n        log.warning(f\"Cannot recognize template {name}, falling back to 'default'.\")\n        name = \"default\"\n    return templates_dict[name]\n\n\n@lru_cache\ndef get_tex_config(template: str = \"\") -> tuple[str, str]:\n    \"\"\"\n    Returns a compiler and preamble to use for rendering LaTeX\n    \"\"\"\n    template = template or manim_config.tex.template\n    config = get_tex_template_config(template)\n    return config[\"compiler\"], config[\"preamble\"]\n\n\ndef get_full_tex(content: str, preamble: str = \"\"):\n    return \"\\n\\n\".join((\n        \"\\\\documentclass[preview]{standalone}\",\n        preamble,\n        \"\\\\begin{document}\",\n        content,\n        \"\\\\end{document}\"\n    )) + \"\\n\"\n\n\n@lru_cache(maxsize=128)\ndef latex_to_svg(\n    latex: str,\n    template: str = \"\",\n    additional_preamble: str = \"\",\n    short_tex: str = \"\",\n    show_message_during_execution: bool = True,\n) -> str:\n    \"\"\"Convert LaTeX string to SVG string.\n\n    Args:\n        latex: LaTeX source code\n        template: Path to a template LaTeX file\n        additional_preamble: String including any added \"\\\\usepackage{...}\" style imports\n\n    Returns:\n        str: SVG source code\n\n    Raises:\n        LatexError: If LaTeX compilation fails\n        NotImplementedError: If compiler is not supported\n    \"\"\"\n    if show_message_during_execution:\n        message = f\"Writing {(short_tex or latex)[:70]}...\"\n    else:\n        message = \"\"\n\n    compiler, preamble = get_tex_config(template)\n\n    preamble = \"\\n\".join([preamble, additional_preamble])\n    full_tex = get_full_tex(latex, preamble)\n    return full_tex_to_svg(full_tex, compiler, message)\n\n\n@cache_on_disk\ndef full_tex_to_svg(full_tex: str, compiler: str = \"latex\", message: str = \"\"):\n    if message:\n        print(message, end=\"\\r\")\n\n    if compiler == \"latex\":\n        dvi_ext = \".dvi\"\n    elif compiler == \"xelatex\":\n        dvi_ext = \".xdv\"\n    else:\n        raise NotImplementedError(f\"Compiler '{compiler}' is not implemented\")\n\n    # Use the custom LaTeX cache directory from the config\n    temp_dir = Path(manim_config.directories.latex_cache)\n    temp_dir.mkdir(exist_ok=True) # Create the directory if it does not already exist\n\n    # Define paths for the intermediate TeX and DVI files\n    tex_path = temp_dir / \"working.tex\"\n    dvi_path = tex_path.with_suffix(dvi_ext)\n\n    # Write tex file\n    tex_path.write_text(full_tex)\n\n    # Run latex compiler\n    process = subprocess.run(\n        [\n            compiler,\n            *(['-no-pdf'] if compiler == \"xelatex\" else []),\n            \"-interaction=batchmode\",\n            \"-halt-on-error\",\n            f\"-output-directory={temp_dir}\",\n            tex_path\n        ],\n        capture_output=True,\n        text=True\n    )\n\n    if process.returncode != 0:\n        # Handle error\n        error_str = \"\"\n        log_path = tex_path.with_suffix(\".log\")\n        if log_path.exists():\n            content = log_path.read_text()\n            error_match = re.search(r\"(?<=\\n! ).*\\n.*\\n\", content)\n            if error_match:\n                error_str = error_match.group()\n        raise LatexError(error_str or \"LaTeX compilation failed\")\n\n    # Run dvisvgm and capture output directly\n    process = subprocess.run(\n        [\n            \"dvisvgm\",\n            dvi_path,\n            \"-n\",  # no fonts\n            \"-v\", \"0\",  # quiet\n            \"--stdout\",  # output to stdout instead of file\n        ],\n        capture_output=True\n    )\n\n    # Return SVG string\n    result = process.stdout.decode('utf-8')\n\n    if message:\n        print(\" \" * len(message), end=\"\\r\")\n\n    return result\n\n\nclass LatexError(Exception):\n    pass\n"
  },
  {
    "path": "manimlib/utils/tex_to_symbol_count.py",
    "content": "TEX_TO_SYMBOL_COUNT = {\n    R\"\\!\": 0,\n    R\"\\,\": 0,\n    R\"\\-\": 0,\n    R\"\\/\": 0,\n    R\"\\:\": 0,\n    R\"\\;\": 0,\n    R\"\\>\": 0,\n    R\"\\aa\": 0,\n    R\"\\AA\": 0,\n    R\"\\ae\": 0,\n    R\"\\AE\": 0,\n    R\"\\arccos\": 6,\n    R\"\\arcsin\": 6,\n    R\"\\arctan\": 6,\n    R\"\\arg\": 3,\n    R\"\\author\": 0,\n    R\"\\bf\": 0,\n    R\"\\bibliography\": 0,\n    R\"\\bibliographystyle\": 0,\n    R\"\\big\": 0,\n    R\"\\Big\": 0,\n    R\"\\bigodot\": 4,\n    R\"\\bigoplus\": 5,\n    R\"\\bigskip\": 0,\n    R\"\\bmod\": 3,\n    R\"\\boldmath\": 0,\n    R\"\\bottomfraction\": 2,\n    R\"\\bowtie\": 2,\n    R\"\\cal\": 0,\n    R\"\\cdots\": 3,\n    R\"\\centering\": 0,\n    R\"\\cite\": 2,\n    R\"\\circ\": 1,\n    R\"\\cong\": 2,\n    R\"\\contentsline\": 0,\n    R\"\\cos\": 3,\n    R\"\\cosh\": 4,\n    R\"\\cot\": 3,\n    R\"\\coth\": 4,\n    R\"\\csc\": 3,\n    R\"\\date\": 0,\n    R\"\\dblfloatpagefraction\": 2,\n    R\"\\dbltopfraction\": 2,\n    R\"\\ddots\": 3,\n    R\"\\deg\": 3,\n    R\"\\det\": 3,\n    R\"\\dim\": 3,\n    R\"\\displaystyle\": 0,\n    R\"\\div\": 2,\n    R\"\\doteq\": 2,\n    R\"\\dotfill\": 0,\n    R\"\\dots\": 3,\n    R\"\\emph\": 0,\n    R\"\\exp\": 3,\n    R\"\\fbox\": 4,\n    R\"\\floatpagefraction\": 2,\n    R\"\\flushbottom\": 0,\n    R\"\\footnotesize\": 0,\n    R\"\\footnotetext\": 0,\n    R\"\\frame\": 2,\n    R\"\\framebox\": 4,\n    R\"\\fussy\": 0,\n    R\"\\gcd\": 3,\n    R\"\\ghost\": 0,\n    R\"\\glossary\": 0,\n    R\"\\hfill\": 0,\n    R\"\\hom\": 3,\n    R\"\\hookleftarrow\": 2,\n    R\"\\hookrightarrow\": 2,\n    R\"\\hrulefill\": 0,\n    R\"\\huge\": 0,\n    R\"\\Huge\": 0,\n    R\"\\hyphenation\": 0,\n    R\"\\iff\": 2,\n    R\"\\Im\": 2,\n    R\"\\index\": 0,\n    R\"\\inf\": 3,\n    R\"\\it\": 0,\n    R\"\\ker\": 3,\n    R\"\\l\": 0,\n    R\"\\L\": 0,\n    R\"\\label\": 0,\n    R\"\\large\": 0,\n    R\"\\Large\": 0,\n    R\"\\LARGE\": 0,\n    R\"\\ldots\": 3,\n    R\"\\lefteqn\": 0,\n    R\"\\left\": 0,\n    R\"\\lg\": 2,\n    R\"\\lim\": 3,\n    R\"\\liminf\": 6,\n    R\"\\limsup\": 6,\n    R\"\\linebreak\": 0,\n    R\"\\ln\": 2,\n    R\"\\log\": 3,\n    R\"\\longleftarrow\": 2,\n    R\"\\Longleftarrow\": 2,\n    R\"\\longleftrightarrow\": 2,\n    R\"\\Longleftrightarrow\": 2,\n    R\"\\longmapsto\": 3,\n    R\"\\longrightarrow\": 2,\n    R\"\\Longrightarrow\": 2,\n    R\"\\makebox\": 0,\n    R\"\\mapsto\": 2,\n    R\"\\markright\": 0,\n    R\"\\mathds\": 0,\n    R\"\\mathcal\": 0,\n    R\"\\max\": 3,\n    R\"\\mbox\": 0,\n    R\"\\medskip\": 0,\n    R\"\\min\": 3,\n    R\"\\mit\": 0,\n    R\"\\models\": 2,\n    R\"\\ne\": 2,\n    R\"\\neq\": 2,\n    R\"\\newline\": 0,\n    R\"\\noindent\": 0,\n    R\"\\nolinebreak\": 0,\n    R\"\\nonumber\": 0,\n    R\"\\nopagebreak\": 0,\n    R\"\\normalmarginpar\": 0,\n    R\"\\normalsize\": 0,\n    R\"\\notin\": 2,\n    R\"\\o\": 0,\n    R\"\\O\": 0,\n    R\"\\obeycr\": 0,\n    R\"\\oe\": 0,\n    R\"\\OE\": 0,\n    R\"\\overbrace\": 4,\n    R\"\\pagebreak\": 0,\n    R\"\\pagenumbering\": 0,\n    R\"\\pageref\": 2,\n    R\"\\pmod\": 5,\n    R\"\\Pr\": 2,\n    R\"\\protect\": 0,\n    R\"\\qquad\": 0,\n    R\"\\quad\": 0,\n    R\"\\raggedbottom\": 0,\n    R\"\\raggedleft\": 0,\n    R\"\\raggedright\": 0,\n    R\"\\Re\": 2,\n    R\"\\ref\": 2,\n    R\"\\restorecr\": 0,\n    R\"\\reversemarginpar\": 0,\n    R\"\\right\": 0,\n    R\"\\rm\": 0,\n    R\"\\sc\": 0,\n    R\"\\scriptscriptstyle\": 0,\n    R\"\\scriptsize\": 0,\n    R\"\\scriptstyle\": 0,\n    R\"\\sec\": 3,\n    R\"\\sf\": 0,\n    R\"\\shortstack\": 0,\n    R\"\\sin\": 3,\n    R\"\\sinh\": 4,\n    R\"\\sl\": 0,\n    R\"\\sloppy\": 0,\n    R\"\\small\": 0,\n    R\"\\Small\": 0,\n    R\"\\smallskip\": 0,\n    R\"\\sqrt\": 2,\n    R\"\\ss\": 0,\n    R\"\\sup\": 3,\n    R\"\\tan\": 3,\n    R\"\\tanh\": 4,\n    R\"\\text\": 0,\n    R\"\\textbf\": 0,\n    R\"\\textfraction\": 2,\n    R\"\\textstyle\": 0,\n    R\"\\thicklines\": 0,\n    R\"\\thinlines\": 0,\n    R\"\\thinspace\": 0,\n    R\"\\tiny\": 0,\n    R\"\\title\": 0,\n    R\"\\today\": 15,\n    R\"\\topfraction\": 2,\n    R\"\\tt\": 0,\n    R\"\\typeout\": 0,\n    R\"\\unboldmath\": 0,\n    R\"\\underbrace\": 6,\n    R\"\\underline\": 0,\n    R\"\\value\": 0,\n    R\"\\vdots\": 3,\n    R\"\\vline\": 0\n}"
  },
  {
    "path": "manimlib/window.py",
    "content": "from __future__ import annotations\n\nimport numpy as np\n\nimport moderngl_window as mglw\nfrom moderngl_window.context.pyglet.window import Window as PygletWindow\nfrom moderngl_window.timers.clock import Timer\nfrom functools import wraps\nimport screeninfo\n\nfrom manimlib.constants import ASPECT_RATIO\nfrom manimlib.constants import FRAME_SHAPE\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Callable, TypeVar, Optional\n    from manimlib.scene.scene import Scene\n\n    T = TypeVar(\"T\")\n\n\nclass Window(PygletWindow):\n    fullscreen: bool = False\n    resizable: bool = True\n    gl_version: tuple[int, int] = (3, 3)\n    vsync: bool = True\n    cursor: bool = True\n\n    def __init__(\n        self,\n        scene: Optional[Scene] = None,\n        position_string: str = \"UR\",\n        monitor_index: int = 1,\n        full_screen: bool = False,\n        size: Optional[tuple[int, int]] = None,\n        position: Optional[tuple[int, int]] = None,\n        samples: int = 0\n    ):\n        self.scene = scene\n        self.monitor = self.get_monitor(monitor_index)\n        self.default_size = size or self.get_default_size(full_screen)\n        self.default_position = position or self.position_from_string(position_string)\n        self.pressed_keys = set()\n\n        super().__init__(samples=samples)\n        self.to_default_position()\n\n        if self.scene:\n            self.init_for_scene(scene)\n\n    def init_for_scene(self, scene: Scene):\n        \"\"\"\n        Resets the state and updates the scene associated to this window.\n\n        This is necessary when we want to reuse an *existing* window after a\n        `scene.reload()` was requested, which will create new scene instances.\n        \"\"\"\n        self.pressed_keys.clear()\n        self._has_undrawn_event = True\n\n        self.scene = scene\n        self.title = str(scene)\n\n        self.init_mgl_context()\n\n        self.timer = Timer()\n        self.config = mglw.WindowConfig(ctx=self.ctx, wnd=self, timer=self.timer)\n        mglw.activate_context(window=self, ctx=self.ctx)\n        self.timer.start()\n\n        # This line seems to resync the viewport\n        self.on_resize(*self.size)\n\n    def get_monitor(self, index):\n        try:\n            monitors = screeninfo.get_monitors()\n            return monitors[min(index, len(monitors) - 1)]\n        except screeninfo.ScreenInfoError:\n            # Default fallback\n            return screeninfo.Monitor(width=1920, height=1080)\n\n    def get_default_size(self, full_screen=False):\n        width = self.monitor.width // (1 if full_screen else 2)\n        height = int(width // ASPECT_RATIO)\n        return (width, height)\n\n    def position_from_string(self, position_string):\n        # Alternatively, it might be specified with a string like\n        # UR, OO, DL, etc. specifying what corner it should go to\n        char_to_n = {\"L\": 0, \"U\": 0, \"O\": 1, \"R\": 2, \"D\": 2}\n        size = self.default_size\n        width_diff = self.monitor.width - size[0]\n        height_diff = self.monitor.height - size[1]\n        x_step = char_to_n[position_string[1]] * width_diff // 2\n        y_step = char_to_n[position_string[0]] * height_diff // 2\n        return (self.monitor.x + x_step, -self.monitor.y + y_step)\n\n    def focus(self):\n        \"\"\"\n        Puts focus on this window by hiding and showing it again.\n\n        Note that the pyglet `activate()` method didn't work as expected here,\n        so that's why we have to use this workaround. This will produce a small\n        flicker on the window but at least reliably focuses it. It may also\n        offset the window position slightly.\n        \"\"\"\n        self._window.set_visible(False)\n        self._window.set_visible(True)\n\n    def to_default_position(self):\n        self.position = self.default_position\n        # Hack. Sometimes, namely when configured to open in a separate window,\n        # the window needs to be resized to display correctly.\n        w, h = self.default_size\n        self.size = (w - 1, h - 1)\n        self.size = (w, h)\n\n    # Delegate event handling to scene\n    def pixel_coords_to_space_coords(\n        self,\n        px: int,\n        py: int,\n        relative: bool = False\n    ) -> np.ndarray:\n        if self.scene is None or not hasattr(self.scene, \"frame\"):\n            return np.zeros(3)\n\n        pixel_shape = np.array(self.size)\n        fixed_frame_shape = np.array(FRAME_SHAPE)\n        frame = self.scene.frame\n\n        coords = np.zeros(3)\n        coords[:2] = (fixed_frame_shape / pixel_shape) * np.array([px, py])\n        if not relative:\n            coords[:2] -= 0.5 * fixed_frame_shape\n        return frame.from_fixed_frame_point(coords, relative)\n\n    def has_undrawn_event(self) -> bool:\n        return self._has_undrawn_event\n\n    def swap_buffers(self):\n        super().swap_buffers()\n        self._has_undrawn_event = False\n\n    @staticmethod\n    def note_undrawn_event(func: Callable[..., T]) -> Callable[..., T]:\n        @wraps(func)\n        def wrapper(self, *args, **kwargs):\n            func(self, *args, **kwargs)\n            self._has_undrawn_event = True\n        return wrapper\n\n    @note_undrawn_event\n    def on_mouse_motion(self, x: int, y: int, dx: int, dy: int) -> None:\n        super().on_mouse_motion(x, y, dx, dy)\n        if not self.scene:\n            return\n        point = self.pixel_coords_to_space_coords(x, y)\n        d_point = self.pixel_coords_to_space_coords(dx, dy, relative=True)\n        self.scene.on_mouse_motion(point, d_point)\n\n    @note_undrawn_event\n    def on_mouse_drag(self, x: int, y: int, dx: int, dy: int, buttons: int, modifiers: int) -> None:\n        super().on_mouse_drag(x, y, dx, dy, buttons, modifiers)\n        if not self.scene:\n            return\n        point = self.pixel_coords_to_space_coords(x, y)\n        d_point = self.pixel_coords_to_space_coords(dx, dy, relative=True)\n        self.scene.on_mouse_drag(point, d_point, buttons, modifiers)\n\n    @note_undrawn_event\n    def on_mouse_press(self, x: int, y: int, button: int, mods: int) -> None:\n        super().on_mouse_press(x, y, button, mods)\n        if not self.scene:\n            return\n        point = self.pixel_coords_to_space_coords(x, y)\n        self.scene.on_mouse_press(point, button, mods)\n\n    @note_undrawn_event\n    def on_mouse_release(self, x: int, y: int, button: int, mods: int) -> None:\n        super().on_mouse_release(x, y, button, mods)\n        if not self.scene:\n            return\n        point = self.pixel_coords_to_space_coords(x, y)\n        self.scene.on_mouse_release(point, button, mods)\n\n    @note_undrawn_event\n    def on_mouse_scroll(self, x: int, y: int, x_offset: float, y_offset: float) -> None:\n        super().on_mouse_scroll(x, y, x_offset, y_offset)\n        if not self.scene:\n            return\n        point = self.pixel_coords_to_space_coords(x, y)\n        offset = self.pixel_coords_to_space_coords(x_offset, y_offset, relative=True)\n        self.scene.on_mouse_scroll(point, offset, x_offset, y_offset)\n\n    @note_undrawn_event\n    def on_key_press(self, symbol: int, modifiers: int) -> None:\n        self.pressed_keys.add(symbol)  # Modifiers?\n        super().on_key_press(symbol, modifiers)\n        if not self.scene:\n            return\n        self.scene.on_key_press(symbol, modifiers)\n\n    @note_undrawn_event\n    def on_key_release(self, symbol: int, modifiers: int) -> None:\n        self.pressed_keys.difference_update({symbol})  # Modifiers?\n        super().on_key_release(symbol, modifiers)\n        if not self.scene:\n            return\n        self.scene.on_key_release(symbol, modifiers)\n\n    @note_undrawn_event\n    def on_resize(self, width: int, height: int) -> None:\n        super().on_resize(width, height)\n        if not self.scene:\n            return\n        self.scene.on_resize(width, height)\n\n    @note_undrawn_event\n    def on_show(self) -> None:\n        super().on_show()\n        if not self.scene:\n            return\n        self.scene.on_show()\n\n    @note_undrawn_event\n    def on_hide(self) -> None:\n        super().on_hide()\n        if not self.scene:\n            return\n        self.scene.on_hide()\n\n    @note_undrawn_event\n    def on_close(self) -> None:\n        super().on_close()\n        if not self.scene:\n            return\n        self.scene.on_close()\n\n    def is_key_pressed(self, symbol: int) -> bool:\n        return (symbol in self.pressed_keys)\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"setuptools\", \"wheel\"]"
  },
  {
    "path": "requirements.txt",
    "content": "addict\nappdirs\naudioop-lts; python_version>='3.13'\ncolour\ndiskcache\nipython>=8.18.0\nisosurfaces\nfontTools\nmanimpango>=0.6.0\nmapbox-earcut\nmatplotlib\nmoderngl\nmoderngl_window\nnumpy\nPillow\npydub\npygments\nPyOpenGL\npyperclip\npywavefront\npyyaml\nrich\nscipy\nscreeninfo\nsetuptools\nskia-pathops\nsvgelements>=1.8.1\nsympy\ntrimesh\ntqdm\ntyping-extensions; python_version < \"3.11\"\nvalidators\n"
  },
  {
    "path": "setup.cfg",
    "content": "[metadata]\nname = manimgl\nversion = 1.7.2\nauthor = Grant Sanderson\nauthor_email= grant@3blue1brown.com\ndescription = Animation engine for explanatory math videos\nlong_description = file: README.md\nlong_description_content_type = text/markdown; charset=UTF-8\nhome_page = https://github.com/3b1b/manim\nproject_urls =\n    Bug Tracker = https://github.com/3b1b/manim/issues\n    Documentation = https://3b1b.github.io/manim/\n    Source Code = https://github.com/3b1b/manim\nlicense = MIT\nclassifiers =\n    Development Status :: 4 - Beta\n    License :: OSI Approved :: MIT License\n    Topic :: Scientific/Engineering\n    Topic :: Multimedia :: Video\n    Topic :: Multimedia :: Graphics\n    Programming Language :: Python :: 3.7\n    Programming Language :: Python :: 3.8\n    Programming Language :: Python :: 3.9\n    Programming Language :: Python :: 3.10\n    Programming Language :: Python :: 3 :: Only\n    Natural Language :: English\n\n[options]\npackages = find:\ninclude_package_data = True\ninstall_requires =\n    addict\n    appdirs\n    audioop-lts; python_version >= \"3.13\"\n    colour\n    diskcache\n    ipython>=8.18.0\n    isosurfaces\n    fontTools\n    manimpango>=0.6.0\n    mapbox-earcut\n    matplotlib\n    moderngl\n    moderngl_window\n    numpy\n    Pillow\n    pydub\n    pygments\n    PyOpenGL\n    pyperclip\n    pyyaml\n    rich\n    scipy\n    screeninfo\n    setuptools\n    skia-pathops\n    svgelements>=1.8.1\n    sympy\n    tqdm\n    typing-extensions; python_version < \"3.11\"\n    validators\n\n[options.entry_points]\nconsole_scripts =\n    manimgl = manimlib.__main__:main\n    manim-render = manimlib.__main__:main\n"
  },
  {
    "path": "setup.py",
    "content": "import setuptools\nsetuptools.setup()"
  }
]