Merge branch 'main' into guide

This commit is contained in:
Jason Grace 2024-01-15 11:00:02 -05:00 committed by GitHub
commit fbe933596b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
80 changed files with 2097 additions and 902 deletions

View file

@ -60,7 +60,7 @@ jobs:
if: runner.os == 'Linux'
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: python3-opengl libpango1.0-dev xvfb
packages: python3-opengl libpango1.0-dev xvfb freeglut3-dev
version: 1.0
- name: Install Texlive (Linux)

View file

@ -4,16 +4,9 @@
.. automodule:: {{ fullname }}
{% block attributes %}
{% if attributes %}
.. rubric:: Module Attributes
.. autosummary::
{% for item in attributes %}
{{ item }}
{%- endfor %}
{% endif %}
{% endblock %}
{# SEE manim.utils.docbuild.autoaliasattr_directive #}
{# FOR INFORMATION ABOUT THE CUSTOM autoaliasattr DIRECTIVE! #}
.. autoaliasattr:: {{ fullname }}
{% block classes %}
{% if classes %}

View file

@ -11,6 +11,7 @@ import sys
from pathlib import Path
import manim
from manim.utils.docbuild.module_parsing import parse_module_attributes
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
@ -44,6 +45,7 @@ extensions = [
"sphinxext.opengraph",
"manim.utils.docbuild.manim_directive",
"manim.utils.docbuild.autocolor_directive",
"manim.utils.docbuild.autoaliasattr_directive",
"sphinx.ext.graphviz",
"sphinx.ext.inheritance_diagram",
"sphinxcontrib.programoutput",
@ -54,7 +56,14 @@ extensions = [
autosummary_generate = True
# generate documentation from type hints
ALIAS_DOCS_DICT = parse_module_attributes()[0]
autodoc_typehints = "description"
autodoc_type_aliases = {
alias_name: f"~manim.{module}.{alias_name}"
for module, module_dict in ALIAS_DOCS_DICT.items()
for category_dict in module_dict.values()
for alias_name in category_dict.keys()
}
autoclass_content = "both"
# controls whether functions documented by the autofunction directive

View file

@ -38,14 +38,10 @@ To get an overview of what our community is currently working on, check out
Contributing can be confusing, so here are a few guides:
.. toctree::
:maxdepth: 2
:maxdepth: 3
contributing/development
contributing/docstrings
contributing/references
contributing/examples
contributing/typings
contributing/admonitions
contributing/docs
contributing/testing
contributing/performance
contributing/internationalization

View file

@ -147,7 +147,7 @@ Develop your contribution
Update the docstrings (the text in triple quotation marks) of any functions
or classes you change and include them with any new functions you add.
See the :doc:`documentation guide <docstrings>` for more information about how we
See the :doc:`documentation guide <docs/docstrings>` for more information about how we
prefer our code to be documented. The content of the docstrings will be
rendered in the :doc:`reference manual <../reference>`.
@ -159,6 +159,8 @@ Develop your contribution
As far as development on your local machine goes, these are the main steps you
should follow.
.. _polishing-changes-and-submitting-a-pull-request:
Polishing Changes and Submitting a Pull Request
-----------------------------------------------
@ -243,7 +245,8 @@ sticks to our coding conventions.
a look at the built HTML files to see whether the formatting of the documentation
you added looks as you intended. You can build the documentation locally
by running ``make html`` from the ``docs`` directory. Make sure you have `Graphviz <https://graphviz.org/>`_
installed locally in order to build the inheritance diagrams.
installed locally in order to build the inheritance diagrams. See :doc:`docs` for
more information.
Finally, if the pipeline passes and you are satisfied with your changes: wait for
feedback and iterate over any requested changes. You will likely be asked to

View file

@ -0,0 +1,83 @@
====================
Adding Documentation
====================
Building the documentation
--------------------------
When you clone the Manim repository from GitHub, you can access the
``docs/`` folder which contains the necessary files to build the
documentation.
To build the docs locally, open a CLI, enter the ``docs/`` folder with the
``cd`` command and execute the following depending on your OS:
- Windows: ``./make.bat html``
- macOS and Linux: ``make html``
The first time you build the docs, the process will take several
minutes because it needs to generate all the ``.rst`` (reST:
reStructured Text) files from scratch by reading and parsing all the
Manim content. The process becomes much shorter the next time, as it
rebuilds only the parts which have changed.
Sphinx library and extensions
-----------------------------
Manim uses `Sphinx <https://www.sphinx-doc.org>`_ for building the
docs. It also makes use of Sphinx extensions such as:
- `Autodoc: <https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html>`_
imports Manim's Python source code, extracts its docstrings and
generates documentation from them.
- `Autosummary: <https://www.sphinx-doc.org/en/master/usage/extensions/autosummary.html>`_
a complement to Autodoc which adds a special directive ``autosummary``,
used in Manim to automatically document classes, methods,
attributes, functions, module-level variables and exceptions.
Autosummary makes use of `Jinja templates <https://jinja.palletsprojects.com/templates/>`_,
which Manim defines for autosummarizing classes and modules
inside ``docs/source/_templates/``.
- `Graphviz extension for Sphinx: <https://www.sphinx-doc.org/en/master/usage/extensions/graphviz.html>`_
embeds graphs generated by the `Graphviz <https://graphviz.org/>`_
module, which must be installed in order to render the
inheritance diagrams in the :doc:`/reference`.
- `Napoleon: <https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html>`_
enables Sphinx to read Google style docstrings and, in
particular for Manim, `NumPy style docstrings <https://numpydoc.readthedocs.io/en/latest/format.html>`_
- see :doc:`docs/docstrings` for more information.
Sphinx theme
------------
The theme used for this website is `Furo <https://sphinx-themes.org/sample-sites/furo/>`_.
Custom Sphinx directives
------------------------
Manim implements **custom directives** for use with Autodoc and Autosummary, which
are defined in :mod:`~.docbuild`:
.. currentmodule:: manim.utils.docbuild
.. autosummary::
:toctree: ../reference
autoaliasattr_directive
autocolor_directive
manim_directive
Index
-----
.. toctree::
:maxdepth: 2
docs/admonitions
docs/docstrings
docs/examples
docs/references
docs/typings

View file

@ -9,23 +9,25 @@ Module Index
.. autosummary::
:toctree: ../reference
constants
~utils.bezier
~utils.color
~utils.commands
~utils.config_ops
~utils.deprecation
constants
~utils.debug
~utils.deprecation
~utils.docbuild
~utils.hashing
~utils.ipython_magic
~utils.images
~utils.ipython_magic
~utils.iterables
~utils.paths
~utils.rate_functions
~utils.simple_functions
~utils.sounds
~utils.space_ops
~utils.testing
~utils.tex
~utils.tex_templates
~utils.tex_file_writing
~utils.tex_templates
typing

View file

@ -16,6 +16,7 @@ from ._config import *
from .utils.commands import *
# isort: on
import numpy as np
from .animation.animation import *
from .animation.changing import *

View file

@ -4,6 +4,8 @@ import configparser
from cloup import Context, HelpFormatter, HelpTheme, Style
__all__ = ["parse_cli_ctx"]
def parse_cli_ctx(parser: configparser.SectionProxy) -> Context:
formatter_settings: dict[str, str | int] = {

View file

@ -26,6 +26,8 @@ from rich.theme import Theme
if TYPE_CHECKING:
from pathlib import Path
__all__ = ["make_logger", "parse_theme", "set_file_logger", "JSONFormatter"]
HIGHLIGHTED_KEYWORDS = [ # these keywords are highlighted specially
"Played",
"animations",

View file

@ -20,18 +20,24 @@ import os
import re
import sys
from collections.abc import Mapping, MutableMapping
from enum import EnumMeta
from pathlib import Path
from typing import Any, ClassVar, Iterable, Iterator, NoReturn
from typing import TYPE_CHECKING, Any, ClassVar, Iterable, Iterator, NoReturn
import numpy as np
from typing_extensions import Self
from .. import constants
from ..constants import RendererType
from ..typing import StrPath, Vector3
from ..utils.color import ManimColor
from ..utils.tex import TexTemplate, TexTemplateFromFile
from manim import constants
from manim.constants import RendererType
from manim.utils.color import ManimColor
from manim.utils.tex import TexTemplate, TexTemplateFromFile
if TYPE_CHECKING:
from enum import EnumMeta
from typing_extensions import Self
from manim.typing import StrPath, Vector3D
__all__ = ["config_file_paths", "make_config_parser", "ManimConfig", "ManimFrame"]
def config_file_paths() -> list[Path]:
@ -1145,22 +1151,22 @@ class ManimConfig(MutableMapping):
)
@property
def top(self) -> Vector3:
def top(self) -> Vector3D:
"""Coordinate at the center top of the frame."""
return self.frame_y_radius * constants.UP
@property
def bottom(self) -> Vector3:
def bottom(self) -> Vector3D:
"""Coordinate at the center bottom of the frame."""
return self.frame_y_radius * constants.DOWN
@property
def left_side(self) -> Vector3:
def left_side(self) -> Vector3D:
"""Coordinate at the middle left of the frame."""
return self.frame_x_radius * constants.LEFT
@property
def right_side(self) -> Vector3:
def right_side(self) -> Vector3D:
"""Coordinate at the middle right of the frame."""
return self.frame_x_radius * constants.RIGHT
@ -1801,7 +1807,7 @@ class ManimFrame(Mapping):
"left_side",
"right_side",
}
_CONSTANTS: ClassVar[dict[str, Vector3]] = {
_CONSTANTS: ClassVar[dict[str, Vector3D]] = {
"UP": np.array((0.0, 1.0, 0.0)),
"DOWN": np.array((0.0, -1.0, 0.0)),
"RIGHT": np.array((1.0, 0.0, 0.0)),

View file

@ -18,6 +18,8 @@ __all__ = ["Animation", "Wait", "override_animation"]
from copy import deepcopy
from typing import TYPE_CHECKING, Callable, Iterable, Sequence
from typing_extensions import Self
if TYPE_CHECKING:
from manim.scene.scene import Scene
@ -112,7 +114,7 @@ class Animation:
*args,
use_override=True,
**kwargs,
):
) -> Self:
if isinstance(mobject, Mobject) and use_override:
func = mobject.animation_override_for(cls)
if func is not None:

View file

@ -13,6 +13,8 @@ from ..animation.composition import AnimationGroup
from ..mobject.mobject import Mobject, Updater, _AnimationBuilder
from ..scene.scene import Scene
__all__ = ["ChangeSpeed"]
class ChangeSpeed(Animation):
"""Modifies the speed of passed animation.

View file

@ -26,6 +26,17 @@ If left empty, the default colour will be used.[/red]
"""
RICH_NON_STYLE_ENTRIES: str = ["log.width", "log.height", "log.timestamps"]
__all__ = [
"value_from_string",
"value_from_string",
"is_valid_style",
"replace_keys",
"cfg",
"write",
"show",
"export",
]
def value_from_string(value: str) -> str | int | bool:
"""Extracts the literal of proper datatype from a string.

View file

@ -10,6 +10,8 @@ from typing import Callable
from ..._config import config
__all__ = ["HEALTH_CHECKS"]
HEALTH_CHECKS = []

View file

@ -12,6 +12,8 @@ import cloup
from .checks import HEALTH_CHECKS
__all__ = ["checkhealth"]
@cloup.command(
context_settings=None,

View file

@ -30,6 +30,8 @@ CFG_DEFAULTS = {
"resolution": (854, 480),
}
__all__ = ["select_resolution", "update_cfg", "project", "scene"]
def select_resolution():
"""Prompts input of type click.Choice from user. Presents options from QUALITIES constant.

View file

@ -12,6 +12,8 @@ import cloup
from ...constants import CONTEXT_SETTINGS, EPILOG
from ...plugins.plugins_flags import list_plugins
__all__ = ["plugins"]
@cloup.command(
context_settings=CONTEXT_SETTINGS,

View file

@ -26,6 +26,8 @@ from .global_options import global_options
from .output_options import output_options
from .render_options import render_options
__all__ = ["render"]
@cloup.command(
context_settings=None,

View file

@ -2,6 +2,8 @@ from __future__ import annotations
from cloup import Choice, option, option_group
__all__ = ["ease_of_access_options"]
ease_of_access_options = option_group(
"Ease of access options",
option(

View file

@ -6,6 +6,8 @@ from cloup import Choice, option, option_group
from ... import logger
__all__ = ["global_options"]
def validate_gui_location(ctx, param, value):
if value:

View file

@ -2,6 +2,8 @@ from __future__ import annotations
from cloup import IntRange, Path, option, option_group
__all__ = ["output_options"]
output_options = option_group(
"Output options",
option(

View file

@ -8,6 +8,8 @@ from manim.constants import QUALITIES, RendererType
from ... import logger
__all__ = ["render_options"]
def validate_scene_range(ctx, param, value):
try:

View file

@ -10,7 +10,7 @@ import numpy as np
from cloup import Context
from PIL.Image import Resampling
from manim.typing import Vector3
from manim.typing import Vector3D
__all__ = [
"SCENE_NOT_FOUND_MESSAGE",
@ -123,43 +123,43 @@ RESAMPLING_ALGORITHMS = {
}
# Geometry: directions
ORIGIN: Vector3 = np.array((0.0, 0.0, 0.0))
ORIGIN: Vector3D = np.array((0.0, 0.0, 0.0))
"""The center of the coordinate system."""
UP: Vector3 = np.array((0.0, 1.0, 0.0))
UP: Vector3D = np.array((0.0, 1.0, 0.0))
"""One unit step in the positive Y direction."""
DOWN: Vector3 = np.array((0.0, -1.0, 0.0))
DOWN: Vector3D = np.array((0.0, -1.0, 0.0))
"""One unit step in the negative Y direction."""
RIGHT: Vector3 = np.array((1.0, 0.0, 0.0))
RIGHT: Vector3D = np.array((1.0, 0.0, 0.0))
"""One unit step in the positive X direction."""
LEFT: Vector3 = np.array((-1.0, 0.0, 0.0))
LEFT: Vector3D = np.array((-1.0, 0.0, 0.0))
"""One unit step in the negative X direction."""
IN: Vector3 = np.array((0.0, 0.0, -1.0))
IN: Vector3D = np.array((0.0, 0.0, -1.0))
"""One unit step in the negative Z direction."""
OUT: Vector3 = np.array((0.0, 0.0, 1.0))
OUT: Vector3D = np.array((0.0, 0.0, 1.0))
"""One unit step in the positive Z direction."""
# Geometry: axes
X_AXIS: Vector3 = np.array((1.0, 0.0, 0.0))
Y_AXIS: Vector3 = np.array((0.0, 1.0, 0.0))
Z_AXIS: Vector3 = np.array((0.0, 0.0, 1.0))
X_AXIS: Vector3D = np.array((1.0, 0.0, 0.0))
Y_AXIS: Vector3D = np.array((0.0, 1.0, 0.0))
Z_AXIS: Vector3D = np.array((0.0, 0.0, 1.0))
# Geometry: useful abbreviations for diagonals
UL: Vector3 = UP + LEFT
UL: Vector3D = UP + LEFT
"""One step up plus one step left."""
UR: Vector3 = UP + RIGHT
UR: Vector3D = UP + RIGHT
"""One step up plus one step right."""
DL: Vector3 = DOWN + LEFT
DL: Vector3D = DOWN + LEFT
"""One step down plus one step left."""
DR: Vector3 = DOWN + RIGHT
DR: Vector3D = DOWN + RIGHT
"""One step down plus one step right."""
# Geometry

View file

@ -13,6 +13,8 @@ except ImportError:
from .. import __version__, config
from ..utils.module_ops import scene_classes_from_file
__all__ = ["configure_pygui"]
if dearpygui_imported:
dpg.create_context()
window = dpg.generate_uuid()

View file

@ -67,7 +67,7 @@ if TYPE_CHECKING:
from manim.mobject.mobject import Mobject
from manim.mobject.text.tex_mobject import SingleStringMathTex, Tex
from manim.mobject.text.text_mobject import Text
from manim.typing import CubicBezierPoints, Point3D, QuadraticBezierPoints, Vector
from manim.typing import CubicBezierPoints, Point3D, QuadraticBezierPoints, Vector3D
class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
@ -91,12 +91,12 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
def __init__(
self,
tip_length: float = DEFAULT_ARROW_TIP_LENGTH,
normal_vector: Vector = OUT,
normal_vector: Vector3D = OUT,
tip_style: dict = {},
**kwargs,
) -> None:
self.tip_length: float = tip_length
self.normal_vector: Vector = normal_vector
self.normal_vector: Vector3D = normal_vector
self.tip_style: dict = tip_style
super().__init__(**kwargs)

View file

@ -2,6 +2,8 @@
from __future__ import annotations
from typing import TYPE_CHECKING
import numpy as np
from pathops import Path as SkiaPath
from pathops import PathVerb, difference, intersection, union, xor
@ -9,7 +11,9 @@ from pathops import PathVerb, difference, intersection, union, xor
from manim import config
from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL
from manim.mobject.types.vectorized_mobject import VMobject
from manim.typing import Point2D_Array
if TYPE_CHECKING:
from manim.typing import Point2D_Array, Point3D_Array
from ...constants import RendererType
@ -26,21 +30,21 @@ class _BooleanOps(VMobject, metaclass=ConvertToOpenGL):
self,
points: Point2D_Array,
z_dim: float = 0.0,
) -> list[np.ndarray]:
"""Converts an iterable with coordinates in 2d to 3d by adding
:attr:`z_dim` as the z coordinate.
) -> Point3D_Array:
"""Converts an iterable with coordinates in 2D to 3D by adding
:attr:`z_dim` as the Z coordinate.
Parameters
----------
points:
An iterable which has the coordinates.
An iterable of points.
z_dim:
The default value of z coordinate.
Default value for the Z coordinate.
Returns
-------
Point2D_Array
A list of array converted to 3d.
Point3D_Array
A list of the points converted to 3D.
Example
-------
@ -66,7 +70,7 @@ class _BooleanOps(VMobject, metaclass=ConvertToOpenGL):
Returns
-------
SkiaPath:
SkiaPath
The converted path.
"""
path = SkiaPath()

View file

@ -31,7 +31,7 @@ from manim.utils.color import WHITE
from manim.utils.space_ops import angle_of_vector, line_intersection, normalize
if TYPE_CHECKING:
from manim.typing import Point2D, Point3D, Vector
from manim.typing import Point2D, Point3D, Vector3D
from manim.utils.color import ParsableManimColor
from ..matrix import Matrix # Avoid circular import
@ -107,7 +107,7 @@ class Line(TipableVMobject):
def _pointify(
self,
mob_or_point: Mobject | Point3D,
direction: Vector | None = None,
direction: Vector3D | None = None,
) -> Point3D:
"""Transforms a mobject into its corresponding point. Does nothing if a point is passed.
@ -163,16 +163,16 @@ class Line(TipableVMobject):
self.generate_points()
return super().put_start_and_end_on(start, end)
def get_vector(self) -> Vector:
def get_vector(self) -> Vector3D:
return self.get_end() - self.get_start()
def get_unit_vector(self) -> Vector:
def get_unit_vector(self) -> Vector3D:
return normalize(self.get_vector())
def get_angle(self) -> float:
return angle_of_vector(self.get_vector())
def get_projection(self, point: Point3D) -> Vector:
def get_projection(self, point: Point3D) -> Vector3D:
"""Returns the projection of a point onto a line.
Parameters
@ -579,7 +579,7 @@ class Arrow(Line):
self.add_tip(tip=old_tips[1], at_start=True)
return self
def get_normal_vector(self) -> Vector:
def get_normal_vector(self) -> Vector3D:
"""Returns the normal of a vector.
Examples
@ -632,6 +632,11 @@ class Arrow(Line):
class Vector(Arrow):
"""A vector specialized for use in graphs.
.. caution::
Do not confuse with the :class:`~.Vector2D`,
:class:`~.Vector3D` or :class:`~.VectorND` type aliases,
which are not Mobjects!
Parameters
----------
direction
@ -654,7 +659,7 @@ class Vector(Arrow):
self.add(plane, vector_1, vector_2)
"""
def __init__(self, direction: Vector = RIGHT, buff: float = 0, **kwargs) -> None:
def __init__(self, direction: Vector3D = RIGHT, buff: float = 0, **kwargs) -> None:
self.buff = buff
if len(direction) == 2:
direction = np.hstack([direction, 0])

View file

@ -25,7 +25,7 @@ from manim.mobject.types.vectorized_mobject import VMobject
from manim.utils.space_ops import angle_of_vector
if TYPE_CHECKING:
from manim.typing import Point3D, Vector
from manim.typing import Point3D, Vector3D
class ArrowTip(VMobject, metaclass=ConvertToOpenGL):
@ -149,7 +149,7 @@ class ArrowTip(VMobject, metaclass=ConvertToOpenGL):
return self.points[0]
@property
def vector(self) -> Vector:
def vector(self) -> Vector3D:
r"""The vector pointing from the base point to the tip point.
Examples

View file

@ -55,7 +55,7 @@ from manim.utils.space_ops import angle_of_vector
if TYPE_CHECKING:
from manim.mobject.mobject import Mobject
from manim.typing import ManimFloat, Point2D, Point3D, Vector3
from manim.typing import ManimFloat, Point2D, Point3D, Vector3D
LineType = TypeVar("LineType", bound=Line)
@ -2342,7 +2342,7 @@ class ThreeDAxes(Axes):
y_length: float | None = config.frame_height + 2.5,
z_length: float | None = config.frame_height - 1.5,
z_axis_config: dict[str, Any] | None = None,
z_normal: Vector3 = DOWN,
z_normal: Vector3D = DOWN,
num_axis_pieces: int = 20,
light_source: Sequence[float] = 9 * DOWN + 7 * LEFT + 10 * OUT,
# opengl stuff (?)
@ -2433,7 +2433,7 @@ class ThreeDAxes(Axes):
direction: Sequence[float] = UR,
buff: float = SMALL_BUFF,
rotation: float = PI / 2,
rotation_axis: Vector3 = OUT,
rotation_axis: Vector3D = OUT,
**kwargs,
) -> Mobject:
"""Generate a y-axis label.
@ -2480,11 +2480,11 @@ class ThreeDAxes(Axes):
def get_z_axis_label(
self,
label: float | str | Mobject,
edge: Vector3 = OUT,
direction: Vector3 = RIGHT,
edge: Vector3D = OUT,
direction: Vector3D = RIGHT,
buff: float = SMALL_BUFF,
rotation: float = PI / 2,
rotation_axis: Vector3 = RIGHT,
rotation_axis: Vector3D = RIGHT,
**kwargs: Any,
) -> Mobject:
"""Generate a z-axis label.

View file

@ -56,8 +56,7 @@ if TYPE_CHECKING:
PathFuncType,
Point3D,
Point3D_Array,
Vector,
Vector3,
Vector3D,
)
from ..animation.animation import Animation
@ -1154,7 +1153,7 @@ class Mobject:
for mob in self.family_members_with_points():
func(mob)
def shift(self, *vectors: Vector3) -> Self:
def shift(self, *vectors: Vector3D) -> Self:
"""Shift by the given vectors.
Parameters
@ -1226,14 +1225,14 @@ class Mobject:
)
return self
def rotate_about_origin(self, angle: float, axis: Vector3 = OUT, axes=[]) -> Self:
def rotate_about_origin(self, angle: float, axis: Vector3D = OUT, axes=[]) -> Self:
"""Rotates the :class:`~.Mobject` about the ORIGIN, which is at [0,0,0]."""
return self.rotate(angle, axis, about_point=ORIGIN)
def rotate(
self,
angle: float,
axis: Vector3 = OUT,
axis: Vector3D = OUT,
about_point: Point3D | None = None,
**kwargs,
) -> Self:
@ -1244,7 +1243,7 @@ class Mobject:
)
return self
def flip(self, axis: Vector3 = UP, **kwargs) -> Self:
def flip(self, axis: Vector3D = UP, **kwargs) -> Self:
"""Flips/Mirrors an mobject about its center.
Examples
@ -1390,7 +1389,7 @@ class Mobject:
return self
def align_on_border(
self, direction: Vector3, buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER
self, direction: Vector3D, buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER
) -> Self:
"""Direction just needs to be a vector pointing towards side or
corner in the 2d plane.
@ -1407,7 +1406,7 @@ class Mobject:
return self
def to_corner(
self, corner: Vector3 = DL, buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER
self, corner: Vector3D = DL, buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER
) -> Self:
"""Moves this :class:`~.Mobject` to the given corner of the screen.
@ -1435,7 +1434,7 @@ class Mobject:
return self.align_on_border(corner, buff)
def to_edge(
self, edge: Vector3 = LEFT, buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER
self, edge: Vector3D = LEFT, buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER
) -> Self:
"""Moves this :class:`~.Mobject` to the given edge of the screen,
without affecting its position in the other dimension.
@ -1467,12 +1466,12 @@ class Mobject:
def next_to(
self,
mobject_or_point: Mobject | Point3D,
direction: Vector3 = RIGHT,
direction: Vector3D = RIGHT,
buff: float = DEFAULT_MOBJECT_TO_MOBJECT_BUFFER,
aligned_edge: Vector3 = ORIGIN,
aligned_edge: Vector3D = ORIGIN,
submobject_to_align: Mobject | None = None,
index_of_submobject_to_align: int | None = None,
coor_mask: Vector3 = np.array([1, 1, 1]),
coor_mask: Vector3D = np.array([1, 1, 1]),
) -> Self:
"""Move this :class:`~.Mobject` next to another's :class:`~.Mobject` or Point3D.
@ -1664,22 +1663,22 @@ class Mobject:
return self.rescale_to_fit(depth, 2, stretch=True, **kwargs)
def set_coord(self, value, dim: int, direction: Vector3 = ORIGIN) -> Self:
def set_coord(self, value, dim: int, direction: Vector3D = ORIGIN) -> Self:
curr = self.get_coord(dim, direction)
shift_vect = np.zeros(self.dim)
shift_vect[dim] = value - curr
self.shift(shift_vect)
return self
def set_x(self, x: float, direction: Vector3 = ORIGIN) -> Self:
def set_x(self, x: float, direction: Vector3D = ORIGIN) -> Self:
"""Set x value of the center of the :class:`~.Mobject` (``int`` or ``float``)"""
return self.set_coord(x, 0, direction)
def set_y(self, y: float, direction: Vector3 = ORIGIN) -> Self:
def set_y(self, y: float, direction: Vector3D = ORIGIN) -> Self:
"""Set y value of the center of the :class:`~.Mobject` (``int`` or ``float``)"""
return self.set_coord(y, 1, direction)
def set_z(self, z: float, direction: Vector3 = ORIGIN) -> Self:
def set_z(self, z: float, direction: Vector3D = ORIGIN) -> Self:
"""Set z value of the center of the :class:`~.Mobject` (``int`` or ``float``)"""
return self.set_coord(z, 2, direction)
@ -1692,8 +1691,8 @@ class Mobject:
def move_to(
self,
point_or_mobject: Point3D | Mobject,
aligned_edge: Vector3 = ORIGIN,
coor_mask: Vector3 = np.array([1, 1, 1]),
aligned_edge: Vector3D = ORIGIN,
coor_mask: Vector3D = np.array([1, 1, 1]),
) -> Self:
"""Move center of the :class:`~.Mobject` to certain Point3D."""
if isinstance(point_or_mobject, Mobject):
@ -1998,7 +1997,7 @@ class Mobject:
else:
return np.max(values)
def get_critical_point(self, direction: Vector3) -> Point3D:
def get_critical_point(self, direction: Vector3D) -> Point3D:
"""Picture a box bounding the :class:`~.Mobject`. Such a box has
9 'critical points': 4 corners, 4 edge center, the
center. This returns one of them, along the given direction.
@ -2027,11 +2026,11 @@ class Mobject:
# Pseudonyms for more general get_critical_point method
def get_edge_center(self, direction: Vector3) -> Point3D:
def get_edge_center(self, direction: Vector3D) -> Point3D:
"""Get edge Point3Ds for certain direction."""
return self.get_critical_point(direction)
def get_corner(self, direction: Vector3) -> Point3D:
def get_corner(self, direction: Vector3D) -> Point3D:
"""Get corner Point3Ds for certain direction."""
return self.get_critical_point(direction)
@ -2042,7 +2041,7 @@ class Mobject:
def get_center_of_mass(self) -> Point3D:
return np.apply_along_axis(np.mean, 0, self.get_all_points())
def get_boundary_point(self, direction: Vector3) -> Point3D:
def get_boundary_point(self, direction: Vector3D) -> Point3D:
all_points = self.get_points_defining_boundary()
index = np.argmax(np.dot(all_points, np.array(direction).T))
return all_points[index]
@ -2101,19 +2100,19 @@ class Mobject:
dim,
) - self.reduce_across_dimension(min, dim)
def get_coord(self, dim: int, direction: Vector3 = ORIGIN):
def get_coord(self, dim: int, direction: Vector3D = ORIGIN):
"""Meant to generalize ``get_x``, ``get_y`` and ``get_z``"""
return self.get_extremum_along_dim(dim=dim, key=direction[dim])
def get_x(self, direction: Vector3 = ORIGIN) -> ManimFloat:
def get_x(self, direction: Vector3D = ORIGIN) -> ManimFloat:
"""Returns x Point3D of the center of the :class:`~.Mobject` as ``float``"""
return self.get_coord(0, direction)
def get_y(self, direction: Vector3 = ORIGIN) -> ManimFloat:
def get_y(self, direction: Vector3D = ORIGIN) -> ManimFloat:
"""Returns y Point3D of the center of the :class:`~.Mobject` as ``float``"""
return self.get_coord(1, direction)
def get_z(self, direction: Vector3 = ORIGIN) -> ManimFloat:
def get_z(self, direction: Vector3D = ORIGIN) -> ManimFloat:
"""Returns z Point3D of the center of the :class:`~.Mobject` as ``float``"""
return self.get_coord(2, direction)
@ -2184,7 +2183,7 @@ class Mobject:
return self.match_dim_size(mobject, 2, **kwargs)
def match_coord(
self, mobject: Mobject, dim: int, direction: Vector3 = ORIGIN
self, mobject: Mobject, dim: int, direction: Vector3D = ORIGIN
) -> Self:
"""Match the Point3Ds with the Point3Ds of another :class:`~.Mobject`."""
return self.set_coord(
@ -2208,7 +2207,7 @@ class Mobject:
def align_to(
self,
mobject_or_point: Mobject | Point3D,
direction: Vector3 = ORIGIN,
direction: Vector3D = ORIGIN,
) -> Self:
"""Aligns mobject to another :class:`~.Mobject` in a certain direction.
@ -2263,7 +2262,7 @@ class Mobject:
def arrange(
self,
direction: Vector3 = RIGHT,
direction: Vector3D = RIGHT,
buff: float = DEFAULT_MOBJECT_TO_MOBJECT_BUFFER,
center: bool = True,
**kwargs,
@ -2296,7 +2295,7 @@ class Mobject:
rows: int | None = None,
cols: int | None = None,
buff: float | tuple[float, float] = MED_SMALL_BUFF,
cell_alignment: Vector3 = ORIGIN,
cell_alignment: Vector3D = ORIGIN,
row_alignments: str | None = None, # "ucd"
col_alignments: str | None = None, # "lcr"
row_heights: Iterable[float | None] | None = None,

View file

@ -10,6 +10,8 @@ from manim.mobject.opengl.opengl_vectorized_mobject import OpenGLVMobject
from ...constants import RendererType
__all__ = ["ConvertToOpenGL"]
class ConvertToOpenGL(ABCMeta):
"""Metaclass for swapping (V)Mobject with its OpenGL counterpart at runtime

View file

@ -28,6 +28,32 @@ DEFAULT_DASH_LENGTH = 0.05
DEFAULT_ARROW_TIP_LENGTH = 0.35
DEFAULT_ARROW_TIP_WIDTH = 0.35
__all__ = [
"OpenGLTipableVMobject",
"OpenGLArc",
"OpenGLArcBetweenPoints",
"OpenGLCurvedArrow",
"OpenGLCurvedDoubleArrow",
"OpenGLCircle",
"OpenGLDot",
"OpenGLEllipse",
"OpenGLAnnularSector",
"OpenGLSector",
"OpenGLAnnulus",
"OpenGLLine",
"OpenGLDashedLine",
"OpenGLTangentLine",
"OpenGLElbow",
"OpenGLArrow",
"OpenGLVector",
"OpenGLDoubleArrow",
"OpenGLCubicBezier",
"OpenGLPolygon",
"OpenGLRegularPolygon",
"OpenGLTriangle",
"OpenGLArrowTip",
]
class OpenGLTipableVMobject(OpenGLVMobject):
"""

View file

@ -13,6 +13,8 @@ from PIL.Image import Resampling
from manim.mobject.opengl.opengl_surface import OpenGLSurface, OpenGLTexturedSurface
from manim.utils.images import get_full_raster_image_path
__all__ = ["OpenGLImageMobject"]
class OpenGLImageMobject(OpenGLTexturedSurface):
def __init__(

View file

@ -56,6 +56,9 @@ def affects_shader_info_id(func):
return wrapper
__all__ = ["OpenGLMobject", "OpenGLGroup", "OpenGLPoint", "_AnimationBuilder"]
class OpenGLMobject:
"""Mathematical Object: base class for objects that can be displayed on screen.

View file

@ -12,6 +12,8 @@ from manim.utils.color import BLACK, WHITE, YELLOW, color_gradient, color_to_rgb
from manim.utils.config_ops import _Uniforms
from manim.utils.iterables import resize_with_interpolation
__all__ = ["OpenGLPMobject", "OpenGLPGroup", "OpenGLPMPoint"]
class OpenGLPMobject(OpenGLMobject):
shader_folder = "true_dot"

View file

@ -17,6 +17,8 @@ from manim.utils.images import change_to_rgba_array, get_full_raster_image_path
from manim.utils.iterables import listify
from manim.utils.space_ops import normalize_along_axis
__all__ = ["OpenGLSurface", "OpenGLTexturedSurface"]
class OpenGLSurface(OpenGLMobject):
r"""Creates a Surface.

View file

@ -5,6 +5,8 @@ import numpy as np
from manim.mobject.opengl.opengl_surface import OpenGLSurface
from manim.mobject.opengl.opengl_vectorized_mobject import OpenGLVGroup, OpenGLVMobject
__all__ = ["OpenGLSurfaceMesh"]
class OpenGLSurfaceMesh(OpenGLVGroup):
def __init__(

View file

@ -34,6 +34,15 @@ from manim.utils.space_ops import (
z_to_vector,
)
__all__ = [
"triggers_refreshed_triangulation",
"OpenGLVMobject",
"OpenGLVGroup",
"OpenGLVectorizedPoint",
"OpenGLCurvesAsSubmobjects",
"OpenGLDashedVMobject",
]
def triggers_refreshed_triangulation(func):
@wraps(func)

View file

@ -24,6 +24,8 @@ from ...mobject.types.vectorized_mobject import VMobject
from ...utils.color import BLACK
from ..svg.svg_mobject import VMobjectFromSVGPath
__all__ = ["Brace", "BraceBetweenPoints", "BraceLabel", "ArcBrace"]
class Brace(VMobjectFromSVGPath):
"""Takes a mobject and draws a brace adjacent to it.

View file

@ -26,6 +26,8 @@ from manim.mobject.text.text_mobject import Paragraph
from manim.mobject.types.vectorized_mobject import VGroup
from manim.utils.color import WHITE
__all__ = ["Code"]
class Code(VGroup):
"""A highlighted source code listing.

View file

@ -18,6 +18,8 @@ from manim.mobject.value_tracker import ValueTracker
string_to_mob_map = {}
__all__ = ["DecimalNumber", "Integer", "Variable"]
class DecimalNumber(VMobject, metaclass=ConvertToOpenGL):
"""An mobject representing a decimal number.

View file

@ -79,6 +79,8 @@ TEXT_MOB_SCALE_FACTOR = 0.05
DEFAULT_LINE_SPACING_SCALE = 0.3
TEXT2SVG_ADJUSTMENT_FACTOR = 4.8
__all__ = ["Text", "Paragraph", "MarkupText", "register_font"]
def remove_invisible_chars(mobject: SVGMobject) -> SVGMobject:
"""Function to remove unwanted invisible characters from some mobjects.

View file

@ -22,7 +22,7 @@ from manim.constants import ORIGIN, UP
from manim.utils.space_ops import get_unit_normal
if TYPE_CHECKING:
from manim.typing import Point3D, Vector
from manim.typing import Point3D, Vector3D
def get_3d_vmob_gradient_start_and_end_points(vmob) -> tuple[Point3D, Point3D]:
@ -52,7 +52,7 @@ def get_3d_vmob_end_corner(vmob) -> Point3D:
return vmob.points[get_3d_vmob_end_corner_index(vmob)]
def get_3d_vmob_unit_normal(vmob, point_index: int) -> Vector:
def get_3d_vmob_unit_normal(vmob, point_index: int) -> Vector3D:
n_points = vmob.get_num_points()
if len(vmob.get_anchors()) <= 2:
return np.array(UP)
@ -68,9 +68,9 @@ def get_3d_vmob_unit_normal(vmob, point_index: int) -> Vector:
return unit_normal
def get_3d_vmob_start_corner_unit_normal(vmob) -> Vector:
def get_3d_vmob_start_corner_unit_normal(vmob) -> Vector3D:
return get_3d_vmob_unit_normal(vmob, get_3d_vmob_start_corner_index(vmob))
def get_3d_vmob_end_corner_unit_normal(vmob) -> Vector:
def get_3d_vmob_end_corner_unit_normal(vmob) -> Vector3D:
return get_3d_vmob_unit_normal(vmob, get_3d_vmob_end_corner_index(vmob))

View file

@ -2,7 +2,7 @@
from __future__ import annotations
from manim.typing import Point3D, Vector3
from manim.typing import Point3D, Vector3D
from manim.utils.color import BLUE, BLUE_D, BLUE_E, LIGHT_GREY, WHITE, interpolate_color
__all__ = [
@ -953,7 +953,7 @@ class Line3D(Cylinder):
def pointify(
self,
mob_or_point: Mobject | Point3D,
direction: Vector3 = None,
direction: Vector3D = None,
) -> np.ndarray:
"""Gets a point representing the center of the :class:`Mobjects <.Mobject>`.
@ -1001,7 +1001,7 @@ class Line3D(Cylinder):
def parallel_to(
cls,
line: Line3D,
point: Vector3 = ORIGIN,
point: Vector3D = ORIGIN,
length: float = 5,
**kwargs,
) -> Line3D:
@ -1049,7 +1049,7 @@ class Line3D(Cylinder):
def perpendicular_to(
cls,
line: Line3D,
point: Vector3 = ORIGIN,
point: Vector3D = ORIGIN,
length: float = 5,
**kwargs,
) -> Line3D:

View file

@ -19,6 +19,8 @@ from ...utils.bezier import interpolate
from ...utils.color import WHITE, ManimColor, color_to_int_rgb
from ...utils.images import change_to_rgba_array, get_full_raster_image_path
__all__ = ["ImageMobject", "ImageMobjectFromCamera"]
class AbstractImageMobject(Mobject):
"""

View file

@ -23,6 +23,8 @@ from ...utils.color import (
)
from ...utils.iterables import stretch_array_to_length
__all__ = ["PMobject", "Mobject1D", "Mobject2D", "PGroup", "PointCloudDot", "Point"]
class PMobject(Mobject, metaclass=ConvertToOpenGL):
"""A disc made of a cloud of Dots

View file

@ -62,7 +62,7 @@ if TYPE_CHECKING:
Point3D_Array,
QuadraticBezierPoints,
RGBA_Array_Float,
Vector3,
Vector3D,
Zeros,
)
@ -74,6 +74,16 @@ if TYPE_CHECKING:
# - Think about length of self.points. Always 0 or 1 mod 4?
# That's kind of weird.
__all__ = [
"VMobject",
"VGroup",
"VDict",
"VectorizedPoint",
"CurvesAsSubmobjects",
"VectorizedPoint",
"DashedVMobject",
]
class VMobject(Mobject):
"""A vectorized mobject.
@ -114,7 +124,7 @@ class VMobject(Mobject):
background_stroke_width: float = 0,
sheen_factor: float = 0.0,
joint_type: LineJointType | None = None,
sheen_direction: Vector3 = UL,
sheen_direction: Vector3D = UL,
close_new_points: bool = False,
pre_function_handle_to_anchor_scale_factor: float = 0.01,
make_smooth_after_applying_functions: bool = False,
@ -139,7 +149,7 @@ class VMobject(Mobject):
self.joint_type: LineJointType = (
LineJointType.AUTO if joint_type is None else joint_type
)
self.sheen_direction: Vector3 = sheen_direction
self.sheen_direction: Vector3D = sheen_direction
self.close_new_points: bool = close_new_points
self.pre_function_handle_to_anchor_scale_factor: float = (
pre_function_handle_to_anchor_scale_factor
@ -386,7 +396,7 @@ class VMobject(Mobject):
background_stroke_width: float | None = None,
background_stroke_opacity: float | None = None,
sheen_factor: float | None = None,
sheen_direction: Vector3 | None = None,
sheen_direction: Vector3D | None = None,
background_image: Image | str | None = None,
family: bool = True,
) -> Self:
@ -553,7 +563,7 @@ class VMobject(Mobject):
color = property(get_color, set_color)
def set_sheen_direction(self, direction: Vector3, family: bool = True) -> Self:
def set_sheen_direction(self, direction: Vector3D, family: bool = True) -> Self:
"""Sets the direction of the applied sheen.
Parameters
@ -578,11 +588,11 @@ class VMobject(Mobject):
for submob in self.get_family():
submob.sheen_direction = direction
else:
self.sheen_direction: Vector3 = direction
self.sheen_direction: Vector3D = direction
return self
def rotate_sheen_direction(
self, angle: float, axis: Vector3 = OUT, family: bool = True
self, angle: float, axis: Vector3D = OUT, family: bool = True
) -> Self:
"""Rotates the direction of the applied sheen.
@ -615,7 +625,7 @@ class VMobject(Mobject):
return self
def set_sheen(
self, factor: float, direction: Vector3 | None = None, family: bool = True
self, factor: float, direction: Vector3D | None = None, family: bool = True
) -> Self:
"""Applies a color gradient from a direction.
@ -653,7 +663,7 @@ class VMobject(Mobject):
self.set_fill(self.get_fill_color(), family=family)
return self
def get_sheen_direction(self) -> Vector3:
def get_sheen_direction(self) -> Vector3D:
return np.array(self.sheen_direction)
def get_sheen_factor(self) -> float:
@ -1028,7 +1038,7 @@ class VMobject(Mobject):
def rotate(
self,
angle: float,
axis: Vector3 = OUT,
axis: Vector3D = OUT,
about_point: Point3D | None = None,
**kwargs,
) -> Self:
@ -1993,8 +2003,14 @@ class VGroup(VMobject, metaclass=ConvertToOpenGL):
(gr-circle_red).animate.shift(RIGHT)
)
"""
if not all(isinstance(m, (VMobject, OpenGLVMobject)) for m in vmobjects):
raise TypeError("All submobjects must be of type VMobject")
for m in vmobjects:
if not isinstance(m, (VMobject, OpenGLVMobject)):
raise TypeError(
f"All submobjects of {self.__class__.__name__} must be of type VMobject. "
f"Got {repr(m)} ({type(m).__name__}) instead. "
"You can try using `Group` instead."
)
return super().add(*vmobjects)
def __add__(self, vmobject: VMobject) -> Self:

View file

@ -20,6 +20,8 @@ if typing.TYPE_CHECKING:
from manim.animation.animation import Animation
from manim.scene.scene import Scene
__all__ = ["CairoRenderer"]
class CairoRenderer:
"""A renderer using Cairo.

View file

@ -39,6 +39,8 @@ from .vectorized_mobject_rendering import (
render_opengl_vectorized_mobject_stroke,
)
__all__ = ["OpenGLCamera", "OpenGLRenderer"]
class OpenGLCamera(OpenGLMobject):
euler_angles = _Data()

View file

@ -7,6 +7,8 @@ from screeninfo import get_monitors
from .. import __version__, config
__all__ = ["Window"]
class Window(PygletWindow):
fullscreen = False

View file

@ -15,6 +15,8 @@ from .. import logger
# of a dict holding all the relevant information
# to that shader
__all__ = ["ShaderWrapper"]
def get_shader_dir():
return Path(__file__).parent / "shaders"

View file

@ -8,6 +8,11 @@ from ..utils import opengl
from ..utils.space_ops import cross2d, earclip_triangulation
from .shader import Shader
__all__ = [
"render_opengl_vectorized_mobject_fill",
"render_opengl_vectorized_mobject_stroke",
]
def build_matrix_lists(mob):
root_hierarchical_matrix = mob.hierarchical_model_matrix()

View file

@ -8,6 +8,8 @@ from typing import Any
from manim import get_video_metadata
__all__ = ["Section", "DefaultSectionType"]
class DefaultSectionType(str, Enum):
"""The type of a section can be used for third party applications.

View file

@ -1,133 +1,633 @@
"""Custom type definitions used in Manim.
.. admonition:: Note for developers
:class: important
Around the source code there are multiple strings which look like this:
.. code-block::
'''
[CATEGORY]
<category_name>
'''
All type aliases defined under those strings will be automatically
classified under that category.
If you need to define a new category, respect the format described above.
"""
from __future__ import annotations
from os import PathLike
from typing import Callable, Tuple, Union
from typing import Callable, Literal, Union
import numpy as np
import numpy.typing as npt
from typing_extensions import TypeAlias
# Color Types
__all__ = [
"ManimFloat",
"ManimInt",
"ManimColorDType",
"RGB_Array_Float",
"RGB_Tuple_Float",
"RGB_Array_Int",
"RGB_Tuple_Int",
"RGBA_Array_Float",
"RGBA_Tuple_Float",
"RGBA_Array_Int",
"RGBA_Tuple_Int",
"HSV_Array_Float",
"HSV_Tuple_Float",
"ManimColorInternal",
"PointDType",
"InternalPoint2D",
"Point2D",
"InternalPoint2D_Array",
"Point2D_Array",
"InternalPoint3D",
"Point3D",
"InternalPoint3D_Array",
"Point3D_Array",
"Vector2D",
"Vector2D_Array",
"Vector3D",
"Vector3D_Array",
"VectorND",
"VectorND_Array",
"RowVector",
"ColVector",
"MatrixMN",
"Zeros",
"QuadraticBezierPoints",
"QuadraticBezierPoints_Array",
"QuadraticBezierPath",
"QuadraticSpline",
"CubicBezierPoints",
"CubicBezierPoints_Array",
"CubicBezierPath",
"CubicSpline",
"BezierPoints",
"BezierPoints_Array",
"BezierPath",
"Spline",
"FlatBezierPoints",
"FunctionOverride",
"PathFuncType",
"MappingFunction",
"Image",
"GrayscaleImage",
"RGBImage",
"RGBAImage",
"StrPath",
"StrOrBytesPath",
]
"""
[CATEGORY]
Primitive data types
"""
ManimFloat: TypeAlias = np.float64
ManimInt: TypeAlias = np.int64
ManimColorDType: TypeAlias = ManimFloat
"""A double-precision floating-point value (64 bits, or 8 bytes),
according to the IEEE 754 standard.
"""
RGB_Array_Float: TypeAlias = npt.NDArray[ManimFloat]
RGB_Tuple_Float: TypeAlias = Tuple[float, float, float]
ManimInt: TypeAlias = np.int64
r"""A long integer (64 bits, or 8 bytes).
It can take values between :math:`-2^{63}` and :math:`+2^{63} - 1`,
which expressed in base 10 is a range between around
:math:`-9.223 \cdot 10^{18}` and :math:`+9.223 \cdot 10^{18}`.
"""
"""
[CATEGORY]
Color types
"""
ManimColorDType: TypeAlias = ManimFloat
"""Data type used in :class:`~.ManimColorInternal`: a
double-precision float between 0 and 1.
"""
RGB_Array_Float: TypeAlias = npt.NDArray[ManimColorDType]
"""``shape: (3,)``
A :class:`numpy.ndarray` of 3 floats between 0 and 1, representing a
color in RGB format.
Its components describe, in order, the intensity of Red, Green, and
Blue in the represented color.
"""
RGB_Tuple_Float: TypeAlias = tuple[float, float, float]
"""``shape: (3,)``
A tuple of 3 floats between 0 and 1, representing a color in RGB
format.
Its components describe, in order, the intensity of Red, Green, and
Blue in the represented color.
"""
RGB_Array_Int: TypeAlias = npt.NDArray[ManimInt]
RGB_Tuple_Int: TypeAlias = Tuple[int, int, int]
"""``shape: (3,)``
RGBA_Array_Float: TypeAlias = npt.NDArray[ManimFloat]
RGBA_Tuple_Float: TypeAlias = Tuple[float, float, float, float]
A :class:`numpy.ndarray` of 3 integers between 0 and 255,
representing a color in RGB format.
Its components describe, in order, the intensity of Red, Green, and
Blue in the represented color.
"""
RGB_Tuple_Int: TypeAlias = tuple[int, int, int]
"""``shape: (3,)``
A tuple of 3 integers between 0 and 255, representing a color in RGB
format.
Its components describe, in order, the intensity of Red, Green, and
Blue in the represented color.
"""
RGBA_Array_Float: TypeAlias = npt.NDArray[ManimColorDType]
"""``shape: (4,)``
A :class:`numpy.ndarray` of 4 floats between 0 and 1, representing a
color in RGBA format.
Its components describe, in order, the intensity of Red, Green, Blue
and Alpha (opacity) in the represented color.
"""
RGBA_Tuple_Float: TypeAlias = tuple[float, float, float, float]
"""``shape: (4,)``
A tuple of 4 floats between 0 and 1, representing a color in RGBA
format.
Its components describe, in order, the intensity of Red, Green, Blue
and Alpha (opacity) in the represented color.
"""
RGBA_Array_Int: TypeAlias = npt.NDArray[ManimInt]
RGBA_Tuple_Int: TypeAlias = Tuple[int, int, int, int]
"""``shape: (4,)``
A :class:`numpy.ndarray` of 4 integers between 0 and 255,
representing a color in RGBA format.
Its components describe, in order, the intensity of Red, Green, Blue
and Alpha (opacity) in the represented color.
"""
RGBA_Tuple_Int: TypeAlias = tuple[int, int, int, int]
"""``shape: (4,)``
A tuple of 4 integers between 0 and 255, representing a color in RGBA
format.
Its components describe, in order, the intensity of Red, Green, Blue
and Alpha (opacity) in the represented color.
"""
HSV_Array_Float: TypeAlias = RGB_Array_Float
"""``shape: (3,)``
A :class:`numpy.ndarray` of 3 floats between 0 and 1, representing a
color in HSV (or HSB) format.
Its components describe, in order, the Hue, Saturation and Value (or
Brightness) in the represented color.
"""
HSV_Tuple_Float: TypeAlias = RGB_Tuple_Float
"""``shape: (3,)``
ManimColorInternal: TypeAlias = npt.NDArray[ManimColorDType]
A tuple of 3 floats between 0 and 1, representing a color in HSV (or
HSB) format.
# Point Types
Its components describe, in order, the Hue, Saturation and Value (or
Brightness) in the represented color.
"""
ManimColorInternal: TypeAlias = RGBA_Array_Float
"""``shape: (4,)``
Internal color representation used by :class:`~.ManimColor`,
following the RGBA format.
It is a :class:`numpy.ndarray` consisting of 4 floats between 0 and
1, describing respectively the intensities of Red, Green, Blue and
Alpha (opacity) in the represented color.
"""
"""
[CATEGORY]
Point types
"""
PointDType: TypeAlias = ManimFloat
""" DType for all points. """
"""Default type for arrays representing points: a double-precision
floating point value.
"""
InternalPoint2D: TypeAlias = npt.NDArray[PointDType]
""" `shape: (2,)` A 2D point. `[float, float]`.
This type alias is mostly made available for internal use and only includes the numpy type.
"""``shape: (2,)``
A 2-dimensional point: ``[float, float]``.
.. note::
This type alias is mostly made available for internal use, and
only includes the NumPy type.
"""
Point2D: TypeAlias = Union[InternalPoint2D, Tuple[float, float]]
""" `shape: (2,)` A 2D point. `[float, float]`. """
Point2D: TypeAlias = Union[InternalPoint2D, tuple[float, float]]
"""``shape: (2,)``
A 2-dimensional point: ``[float, float]``.
Normally, a function or method which expects a `Point2D` as a
parameter can handle being passed a `Point3D` instead.
"""
InternalPoint2D_Array: TypeAlias = npt.NDArray[PointDType]
"""``shape: (N, 3)``
An array of `Point2D` objects: ``[[float, float], ...]``.
.. note::
This type alias is mostly made available for internal use, and
only includes the NumPy type.
"""
Point2D_Array: TypeAlias = Union[InternalPoint2D_Array, tuple[Point2D, ...]]
"""``shape: (N, 2)``
An array of `Point2D` objects: ``[[float, float], ...]``.
Normally, a function or method which expects a `Point2D_Array` as a
parameter can handle being passed a `Point3D_Array` instead.
Please refer to the documentation of the function you are using for
further type information.
"""
InternalPoint3D: TypeAlias = npt.NDArray[PointDType]
""" `shape: (3,)` A 3D point. `[float, float, float]`.
This type alias is mostly made available for internal use and only includes the numpy type.
"""``shape: (3,)``
A 3-dimensional point: ``[float, float, float]``.
.. note::
This type alias is mostly made available for internal use, and
only includes the NumPy type.
"""
Point3D: TypeAlias = Union[InternalPoint3D, Tuple[float, float, float]]
""" `shape: (3,)` A 3D point. `[float, float, float]` """
Point3D: TypeAlias = Union[InternalPoint3D, tuple[float, float, float]]
"""``shape: (3,)``
# Bezier Types
QuadraticBezierPoints: TypeAlias = npt.NDArray[PointDType]
""" `shape: (3,3)` An Array of Quadratic Bezier Handles `[[float, float, float], [float, float, float], [float, float, float]]`. """
QuadraticBezierPoints_Array: TypeAlias = npt.NDArray[PointDType]
""" `shape: (N,3,3)` An Array of Quadratic Bezier Handles `[[[float, float, float], [float, float, float], [float, float, float]], ...]`. """
CubicBezierPoints: TypeAlias = npt.NDArray[PointDType]
""" `shape: (4,3)` An Array of Cubic Bezier Handles `[[float, float, float], [float, float, float], [float, float, float], [float, float, float]]`. """
BezierPoints: TypeAlias = npt.NDArray[PointDType]
""" `shape: (N,3)` An Array of Cubic Bezier Handles `[[float, float, float], ...]`.
`N` Is always multiples of the degree of the Bezier curve.
(Please refer to the documentation of the function you are using for further type Information)
"""
FlatBezierPoints: TypeAlias = npt.NDArray[PointDType]
""" `shape: (N)` An Array of Bezier Handles but flattened `[float, ...]`."""
Point2D_Array: TypeAlias = npt.NDArray[PointDType]
""" `shape: (N,2)` An Array of Points in 2D Space `[[float, float], ...]`.
(Please refer to the documentation of the function you are using for further type Information)
A 3-dimensional point: ``[float, float, float]``.
"""
InternalPoint3D_Array: TypeAlias = npt.NDArray[PointDType]
""" `shape: (N,3)` An Array of Points in 3D Space `[[float, float, float], ...]`.
This type alias is mostly made available for internal use and only includes the numpy type.
"""``shape: (N, 3)``
An array of `Point3D` objects: ``[[float, float, float], ...]``.
.. note::
This type alias is mostly made available for internal use, and
only includes the NumPy type.
"""
Point3D_Array: TypeAlias = Union[
InternalPoint3D_Array, Tuple[Tuple[float, float, float], ...]
Point3D_Array: TypeAlias = Union[InternalPoint3D_Array, tuple[Point3D, ...]]
"""``shape: (N, 3)``
An array of `Point3D` objects: ``[[float, float, float], ...]``.
Please refer to the documentation of the function you are using for
further type information.
"""
"""
[CATEGORY]
Vector types
"""
Vector2D: TypeAlias = Point2D
"""``shape: (2,)``
A 2-dimensional vector: ``[float, float]``.
Normally, a function or method which expects a `Vector2D` as a
parameter can handle being passed a `Vector3D` instead.
.. caution::
Do not confuse with the :class:`~.Vector` or :class:`~.Arrow`
VMobjects!
"""
Vector2D_Array: TypeAlias = Point2D_Array
"""``shape: (M, 2)``
An array of `Vector2D` objects: ``[[float, float], ...]``.
Normally, a function or method which expects a `Vector2D_Array` as a
parameter can handle being passed a `Vector3D_Array` instead.
"""
Vector3D: TypeAlias = Point3D
"""``shape: (3,)``
A 3-dimensional vector: ``[float, float, float]``.
.. caution::
Do not confuse with the :class:`~.Vector` or :class:`~.Arrow3D`
VMobjects!
"""
Vector3D_Array: TypeAlias = Point3D_Array
"""``shape: (M, 3)``
An array of `Vector3D` objects: ``[[float, float, float], ...]``.
"""
VectorND: TypeAlias = Union[npt.NDArray[PointDType], tuple[float, ...]]
"""``shape (N,)``
An :math:`N`-dimensional vector: ``[float, ...]``.
.. caution::
Do not confuse with the :class:`~.Vector` VMobject! This type alias
is named "VectorND" instead of "Vector" to avoid potential name
collisions.
"""
VectorND_Array: TypeAlias = Union[npt.NDArray[PointDType], tuple[VectorND, ...]]
"""``shape (M, N)``
An array of `VectorND` objects: ``[[float, ...], ...]``.
"""
RowVector: TypeAlias = Union[npt.NDArray[PointDType], tuple[tuple[float, ...]]]
"""``shape: (1, N)``
A row vector: ``[[float, ...]]``.
"""
ColVector: TypeAlias = Union[npt.NDArray[PointDType], tuple[tuple[float], ...]]
"""``shape: (N, 1)``
A column vector: ``[[float], [float], ...]``.
"""
"""
[CATEGORY]
Matrix types
"""
MatrixMN: TypeAlias = Union[npt.NDArray[PointDType], tuple[tuple[float, ...], ...]]
"""``shape: (M, N)``
A matrix: ``[[float, ...], [float, ...], ...]``.
"""
Zeros: TypeAlias = Union[npt.NDArray[PointDType], tuple[tuple[Literal[0], ...], ...]]
"""``shape: (M, N)``
A `MatrixMN` filled with zeros, typically created with
``numpy.zeros((M, N))``.
"""
"""
[CATEGORY]
Bézier types
"""
QuadraticBezierPoints: TypeAlias = Union[
npt.NDArray[PointDType], tuple[Point3D, Point3D, Point3D]
]
""" `shape: (N,3)` An Array of Points in 3D Space `[[float, float, float], ...]`.
"""``shape: (3, 3)``
(Please refer to the documentation of the function you are using for further type Information)
A `Point3D_Array` of 3 control points for a single quadratic Bézier
curve:
``[[float, float, float], [float, float, float], [float, float, float]]``.
"""
BezierPoints_Array: TypeAlias = npt.NDArray[PointDType]
""" `shape: (N,PPC,3)` An Array of Bezier Handles `[[[float, float, float], ...], ...]`.
`PPC` Is the number of points per bezier curve. `N` Is the number of bezier curves.
(Please refer to the documentation of the function you are using for further type Information)
QuadraticBezierPoints_Array: TypeAlias = Union[
npt.NDArray[PointDType], tuple[QuadraticBezierPoints, ...]
]
"""``shape: (N, 3, 3)``
An array of :math:`N` `QuadraticBezierPoints` objects:
``[[[float, float, float], [float, float, float], [float, float, float]], ...]``.
"""
# Vector Types
Vector3: TypeAlias = npt.NDArray[PointDType]
""" `shape: (3,)` A Vector `[float, float, float]`. """
QuadraticBezierPath: TypeAlias = Point3D_Array
"""``shape: (3*N, 3)``
Vector: TypeAlias = npt.NDArray[PointDType]
""" `shape: (N,)` A Vector `[float, ...]`. """
A `Point3D_Array` of :math:`3N` points, where each one of the
:math:`N` consecutive blocks of 3 points represents a quadratic
Bézier curve:
``[[float, float, float], ...], ...]``.
RowVector: TypeAlias = npt.NDArray[PointDType]
""" `shape: (1,N)` A Row Vector `[[float, ...]]`. """
Please refer to the documentation of the function you are using for
further type information.
"""
ColVector: TypeAlias = npt.NDArray[PointDType]
""" `shape: (N,1)` A Column Vector `[[float], [float], ...]`. """
QuadraticSpline: TypeAlias = QuadraticBezierPath
"""``shape: (3*N, 3)``
MatrixMN: TypeAlias = npt.NDArray[PointDType]
""" `shape: (M,N)` A Matrix `[[float, ...], [float, ...], ...]`. """
A special case of `QuadraticBezierPath` where all the :math:`N`
quadratic Bézier curves are connected, forming a quadratic spline:
``[[float, float, float], ...], ...]``.
Zeros: TypeAlias = npt.NDArray[ManimFloat]
"""A Matrix of Zeros. Typically created with `numpy.zeros((M,N))`"""
Please refer to the documentation of the function you are using for
further type information.
"""
# Due to current limitations (see https://github.com/python/mypy/issues/14656 / 8263), we don't specify the first argument type (Mobject).
FunctionOverride: TypeAlias = Callable[..., None]
"""Function type returning an animation for the specified Mobject."""
CubicBezierPoints: TypeAlias = Union[
npt.NDArray[PointDType], tuple[Point3D, Point3D, Point3D, Point3D]
]
"""``shape: (4, 3)``
A `Point3D_Array` of 4 control points for a single cubic Bézier
curve:
``[[float, float, float], [float, float, float], [float, float, float], [float, float, float]]``.
"""
CubicBezierPoints_Array: TypeAlias = Union[
npt.NDArray[PointDType], tuple[CubicBezierPoints, ...]
]
"""``shape: (N, 4, 3)``
An array of :math:`N` `CubicBezierPoints` objects:
``[[[float, float, float], [float, float, float], [float, float, float], [float, float, float]], ...]``.
"""
CubicBezierPath: TypeAlias = Point3D_Array
"""``shape: (4*N, 3)``
A `Point3D_Array` of :math:`4N` points, where each one of the
:math:`N` consecutive blocks of 4 points represents a cubic Bézier
curve:
``[[float, float, float], ...], ...]``.
Please refer to the documentation of the function you are using for
further type information.
"""
CubicSpline: TypeAlias = CubicBezierPath
"""``shape: (4*N, 3)``
A special case of `CubicBezierPath` where all the :math:`N` cubic
Bézier curves are connected, forming a quadratic spline:
``[[float, float, float], ...], ...]``.
Please refer to the documentation of the function you are using for
further type information.
"""
BezierPoints: TypeAlias = Point3D_Array
r"""``shape: (PPC, 3)``
A `Point3D_Array` of :math:`\text{PPC}` control points
(:math:`\text{PPC: Points Per Curve} = n + 1`) for a single
:math:`n`-th degree Bézier curve:
``[[float, float, float], ...]``.
Please refer to the documentation of the function you are using for
further type information.
"""
BezierPoints_Array: TypeAlias = Union[npt.NDArray[PointDType], tuple[BezierPoints, ...]]
r"""``shape: (N, PPC, 3)``
An array of :math:`N` `BezierPoints` objects containing
:math:`\text{PPC}` `Point3D` objects each
(:math:`\text{PPC: Points Per Curve} = n + 1`):
``[[[float, float, float], ...], ...]``.
Please refer to the documentation of the function you are using for
further type information.
"""
BezierPath: TypeAlias = Point3D_Array
r"""``shape: (PPC*N, 3)``
A `Point3D_Array` of :math:`\text{PPC} \cdot N` points, where each
one of the :math:`N` consecutive blocks of :math:`\text{PPC}` control
points (:math:`\text{PPC: Points Per Curve} = n + 1`) represents a
Bézier curve of :math:`n`-th degree:
``[[float, float, float], ...], ...]``.
Please refer to the documentation of the function you are using for
further type information.
"""
Spline: TypeAlias = BezierPath
r"""``shape: (PPC*N, 3)``
A special case of `BezierPath` where all the :math:`N` Bézier curves
consisting of :math:`\text{PPC}` `Point3D` objects
(:math:`\text{PPC: Points Per Curve} = n + 1`) are connected, forming
an :math:`n`-th degree spline:
``[[float, float, float], ...], ...]``.
Please refer to the documentation of the function you are using for
further type information.
"""
FlatBezierPoints: TypeAlias = Union[npt.NDArray[PointDType], tuple[float, ...]]
"""``shape: (3*PPC*N,)``
A flattened array of Bézier control points:
``[float, ...]``.
"""
# Misc
"""
[CATEGORY]
Function types
"""
# Due to current limitations
# (see https://github.com/python/mypy/issues/14656 / 8263),
# we don't specify the first argument type (Mobject).
# Nor are we able to specify the return type (Animation) since we cannot import
# that here.
FunctionOverride: TypeAlias = Callable
"""Function type returning an :class:`~.Animation` for the specified
:class:`~.Mobject`.
"""
PathFuncType: TypeAlias = Callable[[Point3D, Point3D, float], Point3D]
"""Function mapping two points and an alpha value to a new point"""
"""Function mapping two `Point3D` objects and an alpha value to a new
`Point3D`.
"""
MappingFunction: TypeAlias = Callable[[Point3D], Point3D]
"""A function mapping a Point3D to another Point3D"""
"""A function mapping a `Point3D` to another `Point3D`."""
Image: TypeAlias = np.ndarray
"""An Image"""
StrPath: TypeAlias = "str | PathLike[str]"
StrOrBytesPath: TypeAlias = "str | bytes | PathLike[str] | PathLike[bytes]"
"""
[CATEGORY]
Image types
"""
Image: TypeAlias = npt.NDArray[ManimInt]
"""``shape: (height, width) | (height, width, 3) | (height, width, 4)``
A rasterized image with a height of ``height`` pixels and a width of
``width`` pixels.
Every value in the array is an integer from 0 to 255.
Every pixel is represented either by a single integer indicating its
lightness (for greyscale images), an `RGB_Array_Int` or an
`RGBA_Array_Int`.
"""
GrayscaleImage: TypeAlias = Image
"""``shape: (height, width)``
A 100% opaque grayscale `Image`, where every pixel value is a
`ManimInt` indicating its lightness (black -> gray -> white).
"""
RGBImage: TypeAlias = Image
"""``shape: (height, width, 3)``
A 100% opaque `Image` in color, where every pixel value is an
`RGB_Array_Int` object.
"""
RGBAImage: TypeAlias = Image
"""``shape: (height, width, 4)``
An `Image` in color where pixels can be transparent. Every pixel
value is an `RGBA_Array_Int` object.
"""
"""
[CATEGORY]
Path types
"""
StrPath: TypeAlias = Union[str, PathLike[str]]
"""A string or :class:`os.PathLike` representing a path to a
directory or file.
"""
StrOrBytesPath: TypeAlias = Union[str, bytes, PathLike[str], PathLike[bytes]]
"""A string, bytes or :class:`os.PathLike` object representing a path
to a directory or file.
"""

View file

@ -5,6 +5,8 @@ from typing import Callable
from .. import config, logger
from ..utils.hashing import get_hash_from_play_call
__all__ = ["handle_caching_play"]
def handle_caching_play(func: Callable[..., None]):
"""Decorator that returns a wrapped version of func that will compute

View file

@ -52,7 +52,6 @@ from ...utils.space_ops import normalize
# import manim._config as _config
re_hex = re.compile("((?<=#)|(?<=0x))[A-F0-9]{6,8}", re.IGNORECASE)
@ -729,14 +728,17 @@ ParsableManimColor: TypeAlias = Union[
RGBA_Array_Int,
RGBA_Array_Float,
]
"""ParsableManimColor is the representation for all types that are parsable to a color in manim"""
"""`ParsableManimColor` represents all the types which can be parsed
to a color in Manim.
"""
ManimColorT = TypeVar("ManimColorT", bound=ManimColor)
def color_to_rgb(color: ParsableManimColor) -> RGB_Array_Float:
"""Helper function for use in functional style programming refer to :meth:`to_rgb` in :class:`ManimColor`
"""Helper function for use in functional style programming.
Refer to :meth:`to_rgb` in :class:`ManimColor`.
Parameters
----------

View file

@ -0,0 +1,17 @@
"""Utilities for building the Manim documentation.
For more information about the Manim documentation building, see:
- :doc:`/contributing/development`, specifically the ``Documentation``
bullet point under :ref:`polishing-changes-and-submitting-a-pull-request`
- :doc:`/contributing/docs`
.. autosummary::
:toctree: ../reference
autoaliasattr_directive
autocolor_directive
manim_directive
module_parsing
"""

View file

@ -0,0 +1,197 @@
"""A directive for documenting type aliases and other module-level attributes."""
from __future__ import annotations
from typing import TYPE_CHECKING
from docutils import nodes
from docutils.parsers.rst import Directive
from docutils.statemachine import ViewList
from manim.utils.docbuild.module_parsing import parse_module_attributes
if TYPE_CHECKING:
from sphinx.application import Sphinx
from typing_extensions import TypeAlias
__all__ = ["AliasAttrDocumenter"]
ALIAS_DOCS_DICT, DATA_DICT = parse_module_attributes()
ALIAS_LIST = [
alias_name
for module_dict in ALIAS_DOCS_DICT.values()
for category_dict in module_dict.values()
for alias_name in category_dict.keys()
]
def smart_replace(base: str, alias: str, substitution: str) -> str:
"""Auxiliary function for substituting type aliases into a base
string, when there are overlaps between the aliases themselves.
Parameters
----------
base
The string in which the type aliases will be located and
replaced.
alias
The substring to be substituted.
substitution
The string which will replace every occurrence of ``alias``.
Returns
-------
str
The new string after the alias substitution.
"""
occurrences = []
len_alias = len(alias)
len_base = len(base)
condition = lambda char: (not char.isalnum()) and char != "_"
start = 0
i = 0
while True:
i = base.find(alias, start)
if i == -1:
break
if (i == 0 or condition(base[i - 1])) and (
i + len_alias == len_base or condition(base[i + len_alias])
):
occurrences.append(i)
start = i + len_alias
for o in occurrences[::-1]:
base = base[:o] + substitution + base[o + len_alias :]
return base
def setup(app: Sphinx) -> None:
app.add_directive("autoaliasattr", AliasAttrDocumenter)
class AliasAttrDocumenter(Directive):
"""Directive which replaces Sphinx's Autosummary for module-level
attributes: instead, it manually crafts a new "Type Aliases"
section, where all the module-level attributes which are explicitly
annotated as :class:`TypeAlias` are considered as such, for their
use all around the Manim docs.
These type aliases are separated from the "regular" module-level
attributes, which get their traditional "Module Attributes"
section autogenerated with Sphinx's Autosummary under "Type
Aliases".
See ``docs/source/_templates/autosummary/module.rst`` to watch
this directive in action.
See :func:`~.parse_module_attributes` for more information on how
the modules are parsed to obtain the :class:`TypeAlias` information
and separate it from the other attributes.
"""
objtype = "autoaliasattr"
required_arguments = 1
has_content = True
def run(self) -> list[nodes.Element]:
module_name = self.arguments[0]
# Slice module_name[6:] to remove the "manim." prefix which is
# not present in the keys of the DICTs
module_alias_dict = ALIAS_DOCS_DICT.get(module_name[6:], None)
module_attrs_list = DATA_DICT.get(module_name[6:], None)
content = nodes.container()
# Add "Type Aliases" section
if module_alias_dict is not None:
module_alias_section = nodes.section(ids=[f"{module_name}.alias"])
content += module_alias_section
# Use a rubric (title-like), just like in `module.rst`
module_alias_section += nodes.rubric(text="Type Aliases")
# category_name: str
# category_dict: AliasCategoryDict = dict[str, AliasInfo]
for category_name, category_dict in module_alias_dict.items():
category_section = nodes.section(
ids=[category_name.lower().replace(" ", "_")]
)
module_alias_section += category_section
# category_name can be possibly "" for uncategorized aliases
if category_name:
category_section += nodes.title(text=category_name)
category_alias_container = nodes.container()
category_section += category_alias_container
# alias_name: str
# alias_info: AliasInfo = dict[str, str]
# Contains "definition": str
# Can possibly contain "doc": str
for alias_name, alias_info in category_dict.items():
# Replace all occurrences of type aliases in the
# definition for automatic cross-referencing!
alias_def = alias_info["definition"]
for A in ALIAS_LIST:
alias_def = smart_replace(alias_def, A, f":class:`~.{A}`")
# Using the `.. class::` directive is CRUCIAL, since
# function/method parameters are always annotated via
# classes - therefore Sphinx expects a class
unparsed = ViewList(
[
f".. class:: {alias_name}",
"",
" .. parsed-literal::",
"",
f" {alias_def}",
"",
]
)
if "doc" in alias_info:
# Replace all occurrences of type aliases in
# the docs for automatic cross-referencing!
alias_doc = alias_info["doc"]
for A in ALIAS_LIST:
alias_doc = alias_doc.replace(f"`{A}`", f":class:`~.{A}`")
# Add all the lines with 4 spaces behind, to consider all the
# documentation as a paragraph INSIDE the `.. class::` block
doc_lines = alias_doc.split("\n")
unparsed.extend(ViewList([f" {line}" for line in doc_lines]))
# Parse the reST text into a fresh container
# https://www.sphinx-doc.org/en/master/extdev/markupapi.html#parsing-directive-content-as-rest
alias_container = nodes.container()
self.state.nested_parse(unparsed, 0, alias_container)
category_alias_container += alias_container
# Then, add the traditional "Module Attributes" section
if module_attrs_list is not None:
module_attrs_section = nodes.section(ids=[f"{module_name}.data"])
content += module_attrs_section
# Use the same rubric (title-like) as in `module.rst`
module_attrs_section += nodes.rubric(text="Module Attributes")
# Let Sphinx Autosummary do its thing as always
# Add all the attribute names with 4 spaces behind, so that
# they're considered as INSIDE the `.. autosummary::` block
unparsed = ViewList(
[
".. autosummary::",
*(f" {attr}" for attr in module_attrs_list),
]
)
# Parse the reST text into a fresh container
# https://www.sphinx-doc.org/en/master/extdev/markupapi.html#parsing-directive-content-as-rest
data_container = nodes.container()
self.state.nested_parse(unparsed, 0, data_container)
module_attrs_section += data_container
return [content]

View file

@ -1,13 +1,20 @@
"""A directive for documenting colors in Manim."""
from __future__ import annotations
import inspect
from typing import TYPE_CHECKING
from docutils import nodes
from docutils.parsers.rst import Directive
from sphinx.application import Sphinx
from manim import ManimColor
if TYPE_CHECKING:
from sphinx.application import Sphinx
__all__ = ["ManimColorModuleDocumenter"]
def setup(app: Sphinx) -> None:
app.add_directive("automanimcolormodule", ManimColorModuleDocumenter)
@ -21,9 +28,7 @@ class ManimColorModuleDocumenter(Directive):
def add_directive_header(self, sig: str) -> None:
super().add_directive_header(sig)
def run(
self,
) -> None:
def run(self) -> list[nodes.Element]:
module_name = self.arguments[0]
try:
import importlib

View file

@ -88,6 +88,7 @@ import sys
import textwrap
from pathlib import Path
from timeit import timeit
from typing import TYPE_CHECKING, Any
import jinja2
from docutils import nodes
@ -97,7 +98,13 @@ from docutils.statemachine import StringList
from manim import QUALITIES
from manim import __version__ as manim_version
classnamedict = {}
if TYPE_CHECKING:
from sphinx.application import Sphinx
__all__ = ["ManimDirective"]
classnamedict: dict[str, int] = {}
class SkipManimNode(nodes.Admonition, nodes.Element):
@ -110,13 +117,13 @@ class SkipManimNode(nodes.Admonition, nodes.Element):
pass
def visit(self, node, name=""):
def visit(self: SkipManimNode, node: nodes.Element, name: str = "") -> None:
self.visit_admonition(node, name)
if not isinstance(node[0], nodes.title):
node.insert(0, nodes.title("skip-manim", "Example Placeholder"))
def depart(self, node):
def depart(self: SkipManimNode, node: nodes.Element) -> None:
self.depart_admonition(node)
@ -162,7 +169,7 @@ class ManimDirective(Directive):
}
final_argument_whitespace = True
def run(self):
def run(self) -> list[nodes.Element]:
# Rendering is skipped if the tag skip-manim is present,
# or if we are making the pot-files
should_skip = (
@ -341,7 +348,7 @@ class ManimDirective(Directive):
rendering_times_file_path = Path("../rendering_times.csv")
def _write_rendering_stats(scene_name, run_time, file_name):
def _write_rendering_stats(scene_name: str, run_time: str, file_name: str) -> None:
with rendering_times_file_path.open("a") as file:
csv.writer(file).writerow(
[
@ -352,7 +359,7 @@ def _write_rendering_stats(scene_name, run_time, file_name):
)
def _log_rendering_times(*args):
def _log_rendering_times(*args: tuple[Any]) -> None:
if rendering_times_file_path.exists():
with rendering_times_file_path.open() as file:
data = list(csv.reader(file))
@ -381,12 +388,12 @@ def _log_rendering_times(*args):
print("")
def _delete_rendering_times(*args):
def _delete_rendering_times(*args: tuple[Any]) -> None:
if rendering_times_file_path.exists():
rendering_times_file_path.unlink()
def setup(app):
def setup(app: Sphinx) -> dict[str, Any]:
app.add_node(SkipManimNode, html=(visit, depart))
setup.app = app

View file

@ -0,0 +1,163 @@
"""Read and parse all the Manim modules and extract documentation from them."""
from __future__ import annotations
import ast
from pathlib import Path
from typing_extensions import TypeAlias
__all__ = ["parse_module_attributes"]
AliasInfo: TypeAlias = dict[str, str]
"""Dictionary with a `definition` key containing the definition of
a :class:`TypeAlias` as a string, and optionally a `doc` key containing
the documentation for that alias, if it exists.
"""
AliasCategoryDict: TypeAlias = dict[str, AliasInfo]
"""Dictionary which holds an `AliasInfo` for every alias name in a same
category.
"""
ModuleLevelAliasDict: TypeAlias = dict[str, AliasCategoryDict]
"""Dictionary containing every :class:`TypeAlias` defined in a module,
classified by category in different `AliasCategoryDict` objects.
"""
AliasDocsDict: TypeAlias = dict[str, ModuleLevelAliasDict]
"""Dictionary which, for every module in Manim, contains documentation
about their module-level attributes which are explicitly defined as
:class:`TypeAlias`, separating them from the rest of attributes.
"""
DataDict: TypeAlias = dict[str, list[str]]
"""Type for a dictionary which, for every module, contains a list with
the names of all their DOCUMENTED module-level attributes (identified
by Sphinx via the ``data`` role, hence the name) which are NOT
explicitly defined as :class:`TypeAlias`.
"""
ALIAS_DOCS_DICT: AliasDocsDict = {}
DATA_DICT: DataDict = {}
MANIM_ROOT = Path(__file__).resolve().parent.parent.parent
def parse_module_attributes() -> tuple[AliasDocsDict, DataDict]:
"""Read all files, generate Abstract Syntax Trees from them, and
extract useful information about the type aliases defined in the
files: the category they belong to, their definition and their
description, separating them from the "regular" module attributes.
Returns
-------
ALIAS_DOCS_DICT : `AliasDocsDict`
A dictionary containing the information from all the type
aliases in Manim. See `AliasDocsDict` for more information.
DATA_DICT : `DataDict`
A dictionary containing the names of all DOCUMENTED
module-level attributes which are not a :class:`TypeAlias`.
"""
global ALIAS_DOCS_DICT
global DATA_DICT
if ALIAS_DOCS_DICT or DATA_DICT:
return ALIAS_DOCS_DICT, DATA_DICT
for module_path in MANIM_ROOT.rglob("*.py"):
module_name = module_path.resolve().relative_to(MANIM_ROOT)
module_name = list(module_name.parts)
module_name[-1] = module_name[-1][:-3] # remove .py
module_name = ".".join(module_name)
module_content = module_path.read_text(encoding="utf-8")
# For storing TypeAliases
module_dict: ModuleLevelAliasDict = {}
category_dict: AliasCategoryDict | None = None
alias_info: AliasInfo | None = None
# For storing regular module attributes
data_list: list[str] = []
data_name: str | None = None
for node in ast.iter_child_nodes(ast.parse(module_content)):
# If we encounter a string:
if (
type(node) is ast.Expr
and type(node.value) is ast.Constant
and type(node.value.value) is str
):
string = node.value.value.strip()
# It can be the start of a category
section_str = "[CATEGORY]"
if string.startswith(section_str):
category_name = string[len(section_str) :].strip()
module_dict[category_name] = {}
category_dict = module_dict[category_name]
alias_info = None
# or a docstring of the alias defined before
elif alias_info:
alias_info["doc"] = string
# or a docstring of the module attribute defined before
elif data_name:
data_list.append(data_name)
continue
# If we encounter an assignment annotated as "TypeAlias":
if (
type(node) is ast.AnnAssign
and type(node.annotation) is ast.Name
and node.annotation.id == "TypeAlias"
and type(node.target) is ast.Name
and node.value is not None
):
alias_name = node.target.id
def_node = node.value
# If it's an Union, replace it with vertical bar notation
if (
type(def_node) is ast.Subscript
and type(def_node.value) is ast.Name
and def_node.value.id == "Union"
):
definition = " | ".join(
ast.unparse(elem) for elem in def_node.slice.elts
)
else:
definition = ast.unparse(def_node)
definition = definition.replace("npt.", "")
if category_dict is None:
module_dict[""] = {}
category_dict = module_dict[""]
category_dict[alias_name] = {"definition": definition}
alias_info = category_dict[alias_name]
continue
# If here, the node is not a TypeAlias definition
alias_info = None
# It could still be a module attribute definition.
# Does the assignment have a target of type Name? Then
# it could be considered a definition of a module attribute.
if type(node) is ast.AnnAssign:
target = node.target
elif type(node) is ast.Assign and len(node.targets) == 1:
target = node.targets[0]
else:
target = None
if type(target) is ast.Name:
data_name = target.id
else:
data_name = None
if len(module_dict) > 0:
ALIAS_DOCS_DICT[module_name] = module_dict
if len(data_list) > 0:
DATA_DICT[module_name] = data_list
return ALIAS_DOCS_DICT, DATA_DICT

View file

@ -1,5 +1,11 @@
from __future__ import annotations
__all__ = [
"EndSceneEarlyException",
"RerunSceneException",
"MultiAnimationOverrideException",
]
class EndSceneEarlyException(Exception):
pass

View file

@ -6,6 +6,8 @@ from typing import Iterable
from ..mobject.mobject import Mobject
from ..utils.iterables import remove_list_redundancies
__all__ = ["extract_mobject_family_members"]
def extract_mobject_family_members(
mobjects: Iterable[Mobject],

View file

@ -2,6 +2,11 @@ from __future__ import annotations
import itertools as it
__all__ = [
"extract_mobject_family_members",
"restructure_list_to_exclude_certain_family_members",
]
def extract_mobject_family_members(mobject_list, only_those_with_points=False):
result = list(it.chain(*(mob.get_family() for mob in mobject_list)))

View file

@ -23,6 +23,8 @@ from .. import config, logger
if typing.TYPE_CHECKING:
from manim.scene.scene import Scene
__all__ = ["KEYS_TO_FILTER_OUT", "get_hash_from_play_call", "get_json"]
# Sometimes there are elements that are not suitable for hashing (too long or
# run-dependent). This is used to filter them out.
KEYS_TO_FILTER_OUT = {

View file

@ -15,6 +15,8 @@ from manim.renderer.shader import shader_program_cache
from ..constants import RendererType
__all__ = ["ManimMagic"]
try:
from IPython import get_ipython
from IPython.core.interactiveshell import InteractiveShell

View file

@ -12,6 +12,8 @@ from pathlib import Path
from .. import config, console, constants, logger
from ..scene.scene_file_writer import SceneFileWriter
__all__ = ["scene_classes_from_file"]
def get_module(file_name: Path):
if str(file_name) == "-":

View file

@ -7,6 +7,20 @@ from .. import config
depth = 20
__all__ = [
"matrix_to_shader_input",
"orthographic_projection_matrix",
"perspective_projection_matrix",
"translation_matrix",
"x_rotation_matrix",
"y_rotation_matrix",
"z_rotation_matrix",
"rotate_in_place_matrix",
"rotation_matrix",
"scale_matrix",
"view_matrix",
]
def matrix_to_shader_input(matrix):
return tuple(matrix.T.ravel())

View file

@ -2,7 +2,26 @@
from __future__ import annotations
from manim.typing import Point3D_Array, Vector, Vector3
import itertools as it
from typing import TYPE_CHECKING, Sequence
import numpy as np
from mapbox_earcut import triangulate_float32 as earcut
from scipy.spatial.transform import Rotation
from manim.constants import DOWN, OUT, PI, RIGHT, TAU, UP, RendererType
from manim.utils.iterables import adjacent_pairs
if TYPE_CHECKING:
import numpy.typing as npt
from manim.typing import (
ManimFloat,
Point3D_Array,
Vector2D,
Vector2D_Array,
Vector3D,
)
__all__ = [
"quaternion_mult",
@ -38,22 +57,11 @@ __all__ = [
]
import itertools as it
from typing import Sequence
import numpy as np
from mapbox_earcut import triangulate_float32 as earcut
from scipy.spatial.transform import Rotation
from ..constants import DOWN, OUT, PI, RIGHT, TAU, UP, RendererType
from ..utils.iterables import adjacent_pairs
def norm_squared(v: float) -> float:
return np.dot(v, v)
def cross(v1: Vector3, v2: Vector3) -> Vector3:
def cross(v1: Vector3D, v2: Vector3D) -> Vector3D:
return np.array(
[
v1[1] * v2[2] - v1[2] * v2[1],
@ -114,7 +122,7 @@ def quaternion_from_angle_axis(
Returns
-------
List[float]
list[float]
Gives back a quaternion from the angle and axis
"""
if not axis_normalized:
@ -369,7 +377,7 @@ def normalize_along_axis(array: np.ndarray, axis: np.ndarray) -> np.ndarray:
return array
def get_unit_normal(v1: Vector3, v2: Vector3, tol: float = 1e-6) -> Vector3:
def get_unit_normal(v1: Vector3D, v2: Vector3D, tol: float = 1e-6) -> Vector3D:
"""Gets the unit normal of the vectors.
Parameters
@ -654,8 +662,9 @@ def shoelace_direction(x_y: np.ndarray) -> str:
def cross2d(
a: Sequence[Vector] | Vector, b: Sequence[Vector] | Vector
) -> Sequence[float] | float:
a: Vector2D | Vector2D_Array,
b: Vector2D | Vector2D_Array,
) -> ManimFloat | npt.NDArray[ManimFloat]:
"""Compute the determinant(s) of the passed
vector (sequences).
@ -746,9 +755,14 @@ def earclip_triangulation(verts: np.ndarray, ring_ends: list) -> list:
# Move the ring which j belongs to from the
# attached list to the detached list
new_ring = next(filter(lambda ring: ring[0] <= j < ring[-1], detached_rings))
detached_rings.remove(new_ring)
attached_rings.append(new_ring)
new_ring = next(
(ring for ring in detached_rings if ring[0] <= j < ring[-1]), None
)
if new_ring is not None:
detached_rings.remove(new_ring)
attached_rings.append(new_ring)
else:
raise Exception("Could not find a ring to attach")
# Setup linked list
after = []

View file

@ -0,0 +1,17 @@
"""Utilities for Manim tests using `pytest <https://pytest.org>`_.
For more information about Manim testing, see:
- :doc:`/contributing/development`, specifically the ``Tests`` bullet
point under :ref:`polishing-changes-and-submitting-a-pull-request`
- :doc:`/contributing/testing`
.. autosummary::
:toctree: ../reference
frames_comparison
_frames_testers
_show_diff
_test_class_makers
"""

View file

@ -19,6 +19,8 @@ from manim.utils.tex import TexTemplate
from .. import config, logger
__all__ = ["tex_to_svg_file"]
def tex_hash(expression):
id_str = str(expression)

1333
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -69,11 +69,11 @@ flake8-docstrings = "^1.7.0"
flake8-pytest-style = "^1.7.2"
flake8-simplify = "^0.14.1"
flake8-rst-docstrings = "^0.3.0"
furo = "^2022.06.21"
furo = "^2023.09.10"
gitpython = "^3"
isort = "^5.12.0"
matplotlib = "^3.8.2"
myst-parser = "^0.17.2"
myst-parser = "^2.0.0"
pre-commit = "^3.5.0"
psutil = {version = "^5.8.0", python = "<3.10"}
psutil-wheels = {version = "5.8.0", python = ">=3.10"}
@ -81,10 +81,10 @@ pytest = "^7.4.3"
pygithub = "^2.1.1"
pytest-cov = "^4.1.0"
pytest-xdist = "^2.2" # Using latest gives flaky tests
Sphinx = "^4"
Sphinx = "^7.2.6"
sphinx-copybutton = "^0.5.2"
sphinxcontrib-programoutput = "^0.17"
sphinxext-opengraph = "^0.9.0"
sphinxext-opengraph = "^0.9.1"
types-decorator = "^0.1.7"
types-Pillow = "^10.1.0.2"
types-Pygments = "^2.17.0.0"

View file

@ -1,5 +1,7 @@
from __future__ import annotations
from enum import Enum
from manim import *