Fix Typing (#3086)

* first draft of color class + starting library conversion

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* changed everything to Manim color todo: figure out circular dependency in utils

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* first working draft of new color version

* resolving conflicts

* resolving conflicts

* resolving conflicts

* resolving conflicts

* resolving conflicts

* changed default internal value of ManimColor to np.ndarray[float]

* starting to fix tests

* fixed more tests and changed precision of manim color

* removed premature color conversion

* fixed some more tests

* final test changes

* fix doctests

* fix for 3.8

* fixing ManimColor string representation

* removing some unneccesary conversions

* moved community constants to manim_colors.py and added more color standards

* Added typing.py and typed bezier.py, core.py, constants.py  fully

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fixed codeql complaints

* add type ignore for np.allclose

* fixed import in three_dimensions

* added ignore for F401 back again in flake

* added typings to coordinate_systems.py

* Few improvements to `graphing/coordinate_systems.py`

* added some typings to mobject/geometry/line.py

* updated typings for mobject/geometry/line.py

* Add missing imports to `line.py`

* added typings to three_dimensions.py

* Use `FunctionOverride` for animation overrides

Fix type signature of `set_color_by_gradient`

* Remove `TYPE_CHECKING` check

Doc is failing

* Revert "Remove `TYPE_CHECKING` check"

Fails due to circular import

* Use `Self` in `coordinate_systems.py`

* Typehinted mobject.py and updated manim.typing.py

* Typed VMobject

* Type-hinted manim.mobject.geometry

* math.cos->np.cos, etc & fixed incorrect typehints

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix missing annotations import

* TypeAlias fix in typing.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Add ignore errors again to mypy because commits are not possible like this

* Fix last typing issues

* Update docs

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Only type check manim

* Try fixing pre-commit

* fix merge

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix compat

* Fix compat again

* Fix imports compat

* Use union syntax

* Use union syntax

* Fix reduce_across_dimension

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Various test and merge fixes

* Doc fixes

* Last doc fix

* Revert usage of np over math

* Bump numpy version

* Remove obsolete duplicate example

* Fixed Incorrect Typehint in manim.constants

* Fix docstring typo

* More fixes

Use mypy.ini instead of .mypy.ini
Fix more docstrings
Improve types in utils and constants

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* docs fixes

* Add internal aliases

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix compat

* line lengths in .rst file, formatting, typos

* add docstring for space_ops:cross2d

* add some more arrow tip typings (in a non-circular import causing way)

* yes, this can be deleted

* fix formatting of example

* added docstring to bezier::inverse_interpolation

* added docstring + test for bezier::match_interpolate

* some improvements in coordinate_systems

* Vector -> Vector3

* replaced np.ndarray with more appropriate type hints

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Apply feedback

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* revert to previous (new) version

* fix doctest

* fix ReST errors

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Alex Lembcke <alex.lembcke@gmail.com>
Co-authored-by: Viicos <65306057+Viicos@users.noreply.github.com>
Co-authored-by: JasonGrace2282 <aarush.deshpande@gmail.com>
Co-authored-by: Benjamin Hackl <devel@benjamin-hackl.at>
This commit is contained in:
Tristan Schulz 2023-11-04 00:49:11 +01:00 committed by GitHub
commit d77a47a233
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 2156 additions and 1347 deletions

View file

@ -1,6 +1,7 @@
[flake8]
# Exclude the grpc generated code
exclude = ./manim/grpc/gen/*
exclude = ./manim/grpc/gen/*, __pycache__,.git,
per-file-ignores = __init__.py:F401
max-complexity = 15
max-line-length = 88
statistics = True
@ -9,7 +10,7 @@ rst-roles = attr,class,func,meth,mod,obj,ref,doc,exc
rst-directives = manim, SEEALSO, seealso
docstring-convention=numpy
select = A,A00,B,B9,C4,C90,D,E,F,F,PT,RST,SIM,W
select = A,A00,B,B9,C4,C90,D,E,F,F,PT,RST,SIM,W,F401
# General Compatibility
extend-ignore = E203, W503, D202, D212, D213, D404
@ -40,4 +41,4 @@ extend-ignore = E203, W503, D202, D212, D213, D404
# Plug-in: flake8-rst-docstrings
RST201, RST203, RST210, RST212, RST213, RST215,
RST301, RST303,
RST301, RST303, RST499

View file

@ -58,7 +58,7 @@ repos:
flake8-simplify==0.14.1,
]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.4.1
rev: v1.5.1
hooks:
- id: mypy
additional_dependencies:
@ -69,6 +69,7 @@ repos:
types-requests,
types-setuptools,
]
files: ^manim/
- repo: https://github.com/codespell-project/codespell
rev: v2.2.5

View file

@ -2,27 +2,62 @@
Adding Typings
==============
.. warning::
This section is still a work in progress.
Adding type hints to functions and parameters
---------------------------------------------
.. warning::
This section is still a work in progress.
Manim is currently in the process of adding type hints into the library. In this
section, you will find information about the standards used and some general
guidelines.
If you've never used type hints before, this is a good place to get started:
https://realpython.com/python-type-checking/#hello-types.
When adding type hints to manim, there are some guidelines that should be followed:
Typing standards
~~~~~~~~~~~~~~~~
* Coordinates have the typehint ``Sequence[float]``, e.g.
Manim uses `mypy`_ to type check its codebase. You will find a list of
configuration values in the ``mypy.ini`` configuration file.
To be able to use the newest typing features not available in the lowest
supported Python version, make use of `typing_extensions`_.
To be able to use the new Union syntax (``|``) and builtins subscripting, use
the ``from __future__ import annotations`` import.
.. _mypy: https://mypy-lang.org/
.. _typing_extensions: https://pypi.org/project/typing-extensions/
Typing guidelines
~~~~~~~~~~~~~~~~~
* Manim has a dedicated :mod:`~.typing` module where type aliases are provided.
Most of them may seem redundant, in particular the ones related to ``numpy``.
This is in anticipation of the support for shape type hinting
(`related issue <https://github.com/numpy/numpy/issues/16544>`_). Besides the
pending shape support, using the correct type aliases will help users understand
which shape should be used.
* Always use a type hint of ``None`` for functions that does not return
a value (this also applies to ``__init__``), e.g.:
.. code:: py
def set_points_as_corners(self, points: Sequence[float]) -> "VMobject":
"""Given an array of points, set them as corner of the Vmobject."""
def height(self, value) -> None:
self.scale_to_fit_height(value)
* ``**kwargs`` has no typehint
* For variables representing paths, use the ``StrPath`` or ``StrOrBytesPath``
type alias defined in the :mod:`~.typing` module.
* Mobjects have the typehint "Mobject", e.g.
* ``*args`` and ``**kwargs`` shouldn't be left untyped (in most cases you can
use ``Any``).
* Following `PEP 484 <https://peps.python.org/pep-0484/#the-numeric-tower>`_,
use ``float`` instead of ``int | float``.
* Mobjects have the typehint ``Mobject``, e.g.:
.. code:: py
@ -30,74 +65,17 @@ When adding type hints to manim, there are some guidelines that should be follow
"""Match the color with the color of another :class:`~.Mobject`."""
return self.set_color(mobject.get_color())
* Colors have the typehint ``Color``, e.g.
.. code:: py
def set_color(self, color: Color = YELLOW_C, family: bool = True):
"""Condition is function which takes in one arguments, (x, y, z)."""
* As ``float`` and ``Union[int, float]`` are the same, use only ``float``
* For numpy arrays use the typehint ``np.ndarray``
* Functions that does not return a value should get the type hint ``None``. (This annotations help catch the kinds of subtle bugs where you are trying to use a meaningless return value. )
.. code:: py
def height(self, value) -> None:
self.scale_to_fit_height(value)
* Parameters that are None by default should get the type hint ``Optional``
.. code:: py
def rotate(
self,
angle,
axis=OUT,
about_point: Optional[Sequence[float]] = None,
**kwargs,
):
pass
* The ``__init__()`` method always should have None as its return type.
* Functions and lambda functions should get the typehint ``Callable``
* Always parametrize generics (``list[int]`` instead of ``list``,
``type[Any]`` instead of ``type``, etc.). This also applies to callables:
.. code:: py
rate_func: Callable[[float], float] = lambda t: smooth(1 - t)
* Assuming that typical path objects are either Paths or strs, one can use the typehint ``typing.Union[str, pathlib.Path]``
.. note::
As a helper for tool for typesets, you can use `typestring-parser
<https://github.com/Dominik1123/typestring-parser>`_
which can be accessed by first installing it via ``pip`` - ``pip install typestring-parser`` and
then using ``from typestring_parser import parse``.
.. doctest::
:options: +SKIP
>>> from typestring_parser import parse
>>> parse("int")
<class 'int'>
>>> parse("int or str")
typing.Union[int, str]
>>> parse("list of str or str")
typing.Union[typing.List[str], str]
>>> parse("list of (int, str)")
typing.List[typing.Tuple[int, str]]
Missing Sections for typehints are:
-----------------------------------
* Tools for typehinting
* Link to MyPy
* Mypy and numpy import errors: https://realpython.com/python-type-checking/#running-mypy
* Where to find the alias
* When to use Object and when to use "Object".
* The use of a TypeVar on the type hints for copy().
* The definition and use of Protocols (like Sized, or Sequence, or Iterable...)
* When to use ``object`` vs ``Any``
* The use of a TypeVar on the type hints for ``copy()``.
* The definition and use of Protocols (like ``Sized``, ``Sequence``, ``Iterable``...)

View file

@ -20,7 +20,6 @@ __all__ = [
]
parser = make_config_parser()
logger: logging.Logger
# The logger can be accessed from anywhere as manim.logger, or as
# logging.getLogger("manim"). The console must be accessed as manim.console.

View file

@ -26,6 +26,7 @@ from rich.theme import Theme
if TYPE_CHECKING:
from pathlib import Path
HIGHLIGHTED_KEYWORDS = [ # these keywords are highlighted specially
"Played",
"animations",

View file

@ -27,9 +27,9 @@ import numpy as np
from .. import constants
from ..constants import RendererType
from ..typing import StrPath
from ..utils.color import ManimColor
from ..utils.tex import TexTemplate, TexTemplateFromFile
from ..utils.tex_templates import TexTemplateLibrary
def config_file_paths() -> list[Path]:
@ -76,7 +76,7 @@ def config_file_paths() -> list[Path]:
def make_config_parser(
custom_file: str | os.PathLike | None = None,
custom_file: StrPath | None = None,
) -> configparser.ConfigParser:
"""Make a :class:`ConfigParser` object and load any ``.cfg`` files.

View file

@ -6,7 +6,6 @@ __all__ = ["AnimatedBoundary", "TracedPath"]
from typing import Callable
from manim._config import config
from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL
from manim.mobject.types.vectorized_mobject import VGroup, VMobject
from manim.utils.color import (

View file

@ -92,7 +92,7 @@ from ..constants import TAU
from ..mobject.mobject import Group, Mobject
from ..mobject.types.vectorized_mobject import VMobject
from ..utils.bezier import integer_interpolate
from ..utils.rate_functions import double_smooth, linear, smooth
from ..utils.rate_functions import double_smooth, linear
class ShowPartial(Animation):

View file

@ -45,6 +45,7 @@ from manim.mobject.geometry.arc import Circle, Dot
from manim.mobject.geometry.line import Line
from manim.mobject.geometry.polygram import Rectangle
from manim.mobject.geometry.shape_matchers import SurroundingRectangle
from manim.scene.scene import Scene
from .. import config
from ..animation.animation import Animation
@ -313,7 +314,7 @@ class ShowPassingFlash(ShowPartial):
lower = max(lower, 0)
return (lower, upper)
def clean_up_from_scene(self, scene: "Scene") -> None:
def clean_up_from_scene(self, scene: Scene) -> None:
super().clean_up_from_scene(scene)
for submob, start in self.get_all_families_zipped():
submob.pointwise_become_partial(start, 0, 1)

View file

@ -9,7 +9,6 @@ import copy
import itertools as it
import operator as op
import pathlib
import time
from functools import reduce
from typing import Any, Callable, Iterable

View file

@ -10,6 +10,8 @@ import numpy as np
from cloup import Context
from PIL.Image import Resampling
from manim.typing import Vector3
__all__ = [
"SCENE_NOT_FOUND_MESSAGE",
"CHOOSE_NUMBER_MESSAGE",
@ -77,34 +79,34 @@ __all__ = [
]
# Messages
SCENE_NOT_FOUND_MESSAGE: str = """
SCENE_NOT_FOUND_MESSAGE = """
{} is not in the script
"""
CHOOSE_NUMBER_MESSAGE: str = """
CHOOSE_NUMBER_MESSAGE = """
Choose number corresponding to desired scene/arguments.
(Use comma separated list for multiple entries)
Choice(s): """
INVALID_NUMBER_MESSAGE: str = "Invalid scene numbers have been specified. Aborting."
NO_SCENE_MESSAGE: str = """
INVALID_NUMBER_MESSAGE = "Invalid scene numbers have been specified. Aborting."
NO_SCENE_MESSAGE = """
There are no scenes inside that module
"""
# Pango stuff
NORMAL: str = "NORMAL"
ITALIC: str = "ITALIC"
OBLIQUE: str = "OBLIQUE"
BOLD: str = "BOLD"
NORMAL = "NORMAL"
ITALIC = "ITALIC"
OBLIQUE = "OBLIQUE"
BOLD = "BOLD"
# Only for Pango from below
THIN: str = "THIN"
ULTRALIGHT: str = "ULTRALIGHT"
LIGHT: str = "LIGHT"
SEMILIGHT: str = "SEMILIGHT"
BOOK: str = "BOOK"
MEDIUM: str = "MEDIUM"
SEMIBOLD: str = "SEMIBOLD"
ULTRABOLD: str = "ULTRABOLD"
HEAVY: str = "HEAVY"
ULTRAHEAVY: str = "ULTRAHEAVY"
THIN = "THIN"
ULTRALIGHT = "ULTRALIGHT"
LIGHT = "LIGHT"
SEMILIGHT = "SEMILIGHT"
BOOK = "BOOK"
MEDIUM = "MEDIUM"
SEMIBOLD = "SEMIBOLD"
ULTRABOLD = "ULTRABOLD"
HEAVY = "HEAVY"
ULTRAHEAVY = "ULTRAHEAVY"
RESAMPLING_ALGORITHMS = {
"nearest": Resampling.NEAREST,
@ -120,80 +122,80 @@ RESAMPLING_ALGORITHMS = {
}
# Geometry: directions
ORIGIN: np.ndarray = np.array((0.0, 0.0, 0.0))
ORIGIN: Vector3 = np.array((0.0, 0.0, 0.0))
"""The center of the coordinate system."""
UP: np.ndarray = np.array((0.0, 1.0, 0.0))
UP: Vector3 = np.array((0.0, 1.0, 0.0))
"""One unit step in the positive Y direction."""
DOWN: np.ndarray = np.array((0.0, -1.0, 0.0))
DOWN: Vector3 = np.array((0.0, -1.0, 0.0))
"""One unit step in the negative Y direction."""
RIGHT: np.ndarray = np.array((1.0, 0.0, 0.0))
RIGHT: Vector3 = np.array((1.0, 0.0, 0.0))
"""One unit step in the positive X direction."""
LEFT: np.ndarray = np.array((-1.0, 0.0, 0.0))
LEFT: Vector3 = np.array((-1.0, 0.0, 0.0))
"""One unit step in the negative X direction."""
IN: np.ndarray = np.array((0.0, 0.0, -1.0))
IN: Vector3 = np.array((0.0, 0.0, -1.0))
"""One unit step in the negative Z direction."""
OUT: np.ndarray = np.array((0.0, 0.0, 1.0))
OUT: Vector3 = np.array((0.0, 0.0, 1.0))
"""One unit step in the positive Z direction."""
# Geometry: axes
X_AXIS: np.ndarray = np.array((1.0, 0.0, 0.0))
Y_AXIS: np.ndarray = np.array((0.0, 1.0, 0.0))
Z_AXIS: np.ndarray = np.array((0.0, 0.0, 1.0))
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))
# Geometry: useful abbreviations for diagonals
UL: np.ndarray = UP + LEFT
UL: Vector3 = UP + LEFT
"""One step up plus one step left."""
UR: np.ndarray = UP + RIGHT
UR: Vector3 = UP + RIGHT
"""One step up plus one step right."""
DL: np.ndarray = DOWN + LEFT
DL: Vector3 = DOWN + LEFT
"""One step down plus one step left."""
DR: np.ndarray = DOWN + RIGHT
DR: Vector3 = DOWN + RIGHT
"""One step down plus one step right."""
# Geometry
START_X: int = 30
START_Y: int = 20
DEFAULT_DOT_RADIUS: float = 0.08
DEFAULT_SMALL_DOT_RADIUS: float = 0.04
DEFAULT_DASH_LENGTH: float = 0.05
DEFAULT_ARROW_TIP_LENGTH: float = 0.35
START_X = 30
START_Y = 20
DEFAULT_DOT_RADIUS = 0.08
DEFAULT_SMALL_DOT_RADIUS = 0.04
DEFAULT_DASH_LENGTH = 0.05
DEFAULT_ARROW_TIP_LENGTH = 0.35
# Default buffers (padding)
SMALL_BUFF: float = 0.1
MED_SMALL_BUFF: float = 0.25
MED_LARGE_BUFF: float = 0.5
LARGE_BUFF: float = 1
DEFAULT_MOBJECT_TO_EDGE_BUFFER: float = MED_LARGE_BUFF
DEFAULT_MOBJECT_TO_MOBJECT_BUFFER: float = MED_SMALL_BUFF
SMALL_BUFF = 0.1
MED_SMALL_BUFF = 0.25
MED_LARGE_BUFF = 0.5
LARGE_BUFF = 1
DEFAULT_MOBJECT_TO_EDGE_BUFFER = MED_LARGE_BUFF
DEFAULT_MOBJECT_TO_MOBJECT_BUFFER = MED_SMALL_BUFF
# Times in seconds
DEFAULT_POINTWISE_FUNCTION_RUN_TIME: float = 3.0
DEFAULT_WAIT_TIME: float = 1.0
DEFAULT_POINTWISE_FUNCTION_RUN_TIME = 3.0
DEFAULT_WAIT_TIME = 1.0
# Misc
DEFAULT_POINT_DENSITY_2D: int = 25
DEFAULT_POINT_DENSITY_1D: int = 10
DEFAULT_STROKE_WIDTH: int = 4
DEFAULT_FONT_SIZE: float = 48
SCALE_FACTOR_PER_FONT_POINT: float = 1 / 960
DEFAULT_POINT_DENSITY_2D = 25
DEFAULT_POINT_DENSITY_1D = 10
DEFAULT_STROKE_WIDTH = 4
DEFAULT_FONT_SIZE = 48
SCALE_FACTOR_PER_FONT_POINT = 1 / 960
# Mathematical constants
PI: float = np.pi
PI = np.pi
"""The ratio of the circumference of a circle to its diameter."""
TAU: float = 2 * PI
TAU = 2 * PI
"""The ratio of the circumference of a circle to its radius."""
DEGREES: float = TAU / 360
DEGREES = TAU / 360
"""The exchange rate between radians and degrees."""
# Video qualities
@ -236,7 +238,7 @@ QUALITIES: dict[str, dict[str, str | int | None]] = {
},
}
DEFAULT_QUALITY: str = "high_quality"
DEFAULT_QUALITY = "high_quality"
EPILOG = "Made with <3 by Manim Community developers."
SHIFT_VALUE = 65505

View file

@ -43,16 +43,16 @@ __all__ = [
]
import itertools
import math
import warnings
from typing import TYPE_CHECKING, Sequence
from typing import TYPE_CHECKING
import numpy as np
from typing_extensions import Self
from manim.constants import *
from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL
from manim.mobject.types.vectorized_mobject import VMobject
from manim.utils.color import *
from manim.mobject.types.vectorized_mobject import VGroup, VMobject
from manim.utils.color import BLACK, BLUE, RED, WHITE, ParsableManimColor
from manim.utils.iterables import adjacent_pairs
from manim.utils.space_ops import (
angle_of_vector,
@ -63,9 +63,11 @@ from manim.utils.space_ops import (
)
if TYPE_CHECKING:
import manim.mobject.geometry.tips as tips
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
class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
@ -88,21 +90,26 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
def __init__(
self,
tip_length=DEFAULT_ARROW_TIP_LENGTH,
normal_vector=OUT,
tip_style={},
tip_length: float = DEFAULT_ARROW_TIP_LENGTH,
normal_vector: Vector = OUT,
tip_style: dict = {},
**kwargs,
):
self.tip_length = tip_length
self.normal_vector = normal_vector
self.tip_style = tip_style
) -> None:
self.tip_length: float = tip_length
self.normal_vector: Vector = normal_vector
self.tip_style: dict = tip_style
super().__init__(**kwargs)
# Adding, Creating, Modifying tips
def add_tip(
self, tip=None, tip_shape=None, tip_length=None, tip_width=None, at_start=False
):
self,
tip: tips.ArrowTip | None = None,
tip_shape: type[tips.ArrowTip] | None = None,
tip_length: float | None = None,
tip_width: float | None = None,
at_start: bool = False,
) -> Self:
"""Adds a tip to the TipableVMobject instance, recognising
that the endpoints might need to be switched if it's
a 'starting tip' or not.
@ -117,7 +124,11 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
return self
def create_tip(
self, tip_shape=None, tip_length=None, tip_width=None, at_start=False
self,
tip_shape: type[tips.ArrowTip] | None = None,
tip_length: float = None,
tip_width: float = None,
at_start: bool = False,
):
"""Stylises the tip, positions it spatially, and returns
the newly instantiated tip to the caller.
@ -126,7 +137,12 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
self.position_tip(tip, at_start)
return tip
def get_unpositioned_tip(self, tip_shape=None, tip_length=None, tip_width=None):
def get_unpositioned_tip(
self,
tip_shape: type[tips.ArrowTip] | None = None,
tip_length: float | None = None,
tip_width: float | None = None,
):
"""Returns a tip that has been stylistically configured,
but has not yet been given a position in space.
"""
@ -150,7 +166,7 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
tip = tip_shape(length=tip_length, **style)
return tip
def position_tip(self, tip, at_start=False):
def position_tip(self, tip: tips.ArrowTip, at_start: bool = False):
# Last two control points, defining both
# the end, and the tangency direction
if at_start:
@ -177,7 +193,7 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
tip.shift(anchor - tip.tip_point)
return tip
def reset_endpoints_based_on_tip(self, tip, at_start):
def reset_endpoints_based_on_tip(self, tip: tips.ArrowTip, at_start: bool) -> Self:
if self.get_length() == 0:
# Zero length, put_start_and_end_on wouldn't work
return self
@ -188,7 +204,7 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
self.put_start_and_end_on(self.get_start(), tip.base)
return self
def asign_tip_attr(self, tip, at_start):
def asign_tip_attr(self, tip: tips.ArrowTip, at_start: bool) -> Self:
if at_start:
self.start_tip = tip
else:
@ -197,15 +213,15 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
# Checking for tips
def has_tip(self):
def has_tip(self) -> bool:
return hasattr(self, "tip") and self.tip in self
def has_start_tip(self):
def has_start_tip(self) -> bool:
return hasattr(self, "start_tip") and self.start_tip in self
# Getters
def pop_tips(self):
def pop_tips(self) -> VGroup:
start, end = self.get_start_and_end()
result = self.get_group_class()()
if self.has_tip():
@ -217,7 +233,7 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
self.put_start_and_end_on(start, end)
return result
def get_tips(self):
def get_tips(self) -> VGroup:
"""Returns a VGroup (collection of VMobjects) containing
the TipableVMObject instance's tips.
"""
@ -237,28 +253,28 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
else:
return tips[0]
def get_default_tip_length(self):
def get_default_tip_length(self) -> float:
return self.tip_length
def get_first_handle(self):
def get_first_handle(self) -> Point3D:
return self.points[1]
def get_last_handle(self):
def get_last_handle(self) -> Point3D:
return self.points[-2]
def get_end(self):
def get_end(self) -> Point3D:
if self.has_tip():
return self.tip.get_start()
else:
return super().get_end()
def get_start(self):
def get_start(self) -> Point3D:
if self.has_start_tip():
return self.start_tip.get_start()
else:
return super().get_start()
def get_length(self):
def get_length(self) -> np.floating:
start, end = self.get_start_and_end()
return np.linalg.norm(start - end)
@ -283,21 +299,21 @@ class Arc(TipableVMobject):
radius: float = 1.0,
start_angle: float = 0,
angle: float = TAU / 4,
num_components=9,
arc_center=ORIGIN,
num_components: int = 9,
arc_center: Point3D = ORIGIN,
**kwargs,
):
if radius is None: # apparently None is passed by ArcBetweenPoints
radius = 1.0
self.radius = radius
self.num_components = num_components
self.arc_center = arc_center
self.start_angle = start_angle
self.angle = angle
self._failed_to_get_center = False
self.num_components: int = num_components
self.arc_center: Point3D = arc_center
self.start_angle: float = start_angle
self.angle: float = angle
self._failed_to_get_center: bool = False
super().__init__(**kwargs)
def generate_points(self):
def generate_points(self) -> None:
self._set_pre_positioned_points()
self.scale(self.radius, about_point=ORIGIN)
self.shift(self.arc_center)
@ -305,7 +321,7 @@ class Arc(TipableVMobject):
# Points are set a bit differently when rendering via OpenGL.
# TODO: refactor Arc so that only one strategy for setting points
# has to be used.
def init_points(self):
def init_points(self) -> None:
self.set_points(
Arc._create_quadratic_bezier_points(
angle=self.angle,
@ -317,7 +333,9 @@ class Arc(TipableVMobject):
self.shift(self.arc_center)
@staticmethod
def _create_quadratic_bezier_points(angle, start_angle=0, n_components=8):
def _create_quadratic_bezier_points(
angle: float, start_angle: float = 0, n_components: int = 8
) -> QuadraticBezierPoints:
samples = np.array(
[
[np.cos(a), np.sin(a), 0]
@ -337,7 +355,7 @@ class Arc(TipableVMobject):
points[2::3] = samples[2::2]
return points
def _set_pre_positioned_points(self):
def _set_pre_positioned_points(self) -> None:
anchors = np.array(
[
np.cos(a) * RIGHT + np.sin(a) * UP
@ -360,7 +378,7 @@ class Arc(TipableVMobject):
handles2 = anchors[1:] - (d_theta / 3) * tangent_vectors[1:]
self.set_anchors_and_handles(anchors[:-1], handles1, handles2, anchors[1:])
def get_arc_center(self, warning=True):
def get_arc_center(self, warning: bool = True) -> Point3D:
"""Looks at the normals to the first two
anchors, and finds their intersection points
"""
@ -386,11 +404,11 @@ class Arc(TipableVMobject):
self._failed_to_get_center = True
return np.array(ORIGIN)
def move_arc_center_to(self, point):
def move_arc_center_to(self, point: Point3D) -> Self:
self.shift(point - self.get_arc_center())
return self
def stop_angle(self):
def stop_angle(self) -> float:
return angle_of_vector(self.points[-1] - self.get_arc_center()) % TAU
@ -413,7 +431,14 @@ class ArcBetweenPoints(Arc):
self.play(Create(arc))
"""
def __init__(self, start, end, angle=TAU / 4, radius=None, **kwargs):
def __init__(
self,
start: Point3D,
end: Point3D,
angle: float = TAU / 4,
radius: float = None,
**kwargs,
) -> None:
if radius is not None:
self.radius = radius
if radius < 0:
@ -427,8 +452,8 @@ class ArcBetweenPoints(Arc):
"""ArcBetweenPoints called with a radius that is
smaller than half the distance between the points.""",
)
arc_height = radius - math.sqrt(radius**2 - halfdist**2)
angle = math.acos((radius - arc_height) / radius) * sign
arc_height = radius - np.sqrt(radius**2 - halfdist**2)
angle = np.arccos((radius - arc_height) / radius) * sign
super().__init__(radius=radius, angle=angle, **kwargs)
if angle == 0:
@ -440,11 +465,11 @@ class ArcBetweenPoints(Arc):
if not self._failed_to_get_center:
self.radius = np.linalg.norm(np.array(start) - np.array(center))
else:
self.radius = math.inf
self.radius = np.inf
class CurvedArrow(ArcBetweenPoints):
def __init__(self, start_point, end_point, **kwargs):
def __init__(self, start_point: Point3D, end_point: Point3D, **kwargs) -> None:
from manim.mobject.geometry.tips import ArrowTriangleFilledTip
tip_shape = kwargs.pop("tip_shape", ArrowTriangleFilledTip)
@ -453,7 +478,7 @@ class CurvedArrow(ArcBetweenPoints):
class CurvedDoubleArrow(CurvedArrow):
def __init__(self, start_point, end_point, **kwargs):
def __init__(self, start_point: Point3D, end_point: Point3D, **kwargs) -> None:
if "tip_shape_end" in kwargs:
kwargs["tip_shape"] = kwargs.pop("tip_shape_end")
from manim.mobject.geometry.tips import ArrowTriangleFilledTip
@ -493,7 +518,7 @@ class Circle(Arc):
radius: float | None = None,
color: ParsableManimColor = RED,
**kwargs,
):
) -> None:
super().__init__(
radius=radius,
start_angle=0,
@ -508,7 +533,7 @@ class Circle(Arc):
dim_to_match: int = 0,
stretch: bool = False,
buffer_factor: float = 1.2,
):
) -> Self:
"""Modifies a circle so that it surrounds a given mobject.
Parameters
@ -555,7 +580,7 @@ class Circle(Arc):
self.width = np.sqrt(mobject.width**2 + mobject.height**2)
return self.scale(buffer_factor)
def point_at_angle(self, angle: float):
def point_at_angle(self, angle: float) -> Point3D:
"""Returns the position of a point on the circle.
Parameters
@ -587,13 +612,11 @@ class Circle(Arc):
start_angle = angle_of_vector(self.points[0] - self.get_center())
proportion = (angle - start_angle) / TAU
proportion -= math.floor(proportion)
proportion -= np.floor(proportion)
return self.point_from_proportion(proportion)
@staticmethod
def from_three_points(
p1: Sequence[float], p2: Sequence[float], p3: Sequence[float], **kwargs
):
def from_three_points(p1: Point3D, p2: Point3D, p3: Point3D, **kwargs) -> Self:
"""Returns a circle passing through the specified
three points.
@ -653,13 +676,13 @@ class Dot(Circle):
def __init__(
self,
point: list | np.ndarray = ORIGIN,
point: Point3D = ORIGIN,
radius: float = DEFAULT_DOT_RADIUS,
stroke_width: float = 0,
fill_opacity: float = 1.0,
color: ParsableManimColor = WHITE,
**kwargs,
):
) -> None:
super().__init__(
arc_center=point,
radius=radius,
@ -676,11 +699,11 @@ class AnnotationDot(Dot):
def __init__(
self,
radius: float = DEFAULT_DOT_RADIUS * 1.3,
stroke_width=5,
stroke_color=WHITE,
fill_color=BLUE,
stroke_width: float = 5,
stroke_color: ParsableManimColor = WHITE,
fill_color: ParsableManimColor = BLUE,
**kwargs,
):
) -> None:
super().__init__(
radius=radius,
stroke_width=stroke_width,
@ -769,7 +792,7 @@ class Ellipse(Circle):
self.add(ellipse_group)
"""
def __init__(self, width: float = 2, height: float = 1, **kwargs):
def __init__(self, width: float = 2, height: float = 1, **kwargs) -> None:
super().__init__(**kwargs)
self.stretch_to_fit_width(width)
self.stretch_to_fit_height(height)
@ -823,15 +846,15 @@ class AnnularSector(Arc):
def __init__(
self,
inner_radius=1,
outer_radius=2,
angle=TAU / 4,
start_angle=0,
fill_opacity=1,
stroke_width=0,
color=WHITE,
inner_radius: float = 1,
outer_radius: float = 2,
angle: float = TAU / 4,
start_angle: float = 0,
fill_opacity: float = 1,
stroke_width: float = 0,
color: ParsableManimColor = WHITE,
**kwargs,
):
) -> None:
self.inner_radius = inner_radius
self.outer_radius = outer_radius
super().__init__(
@ -843,7 +866,7 @@ class AnnularSector(Arc):
**kwargs,
)
def generate_points(self):
def generate_points(self) -> None:
inner_arc, outer_arc = (
Arc(
start_angle=self.start_angle,
@ -879,7 +902,9 @@ class Sector(AnnularSector):
self.add(sector, sector2)
"""
def __init__(self, outer_radius=1, inner_radius=0, **kwargs):
def __init__(
self, outer_radius: float = 1, inner_radius: float = 0, **kwargs
) -> None:
super().__init__(inner_radius=inner_radius, outer_radius=outer_radius, **kwargs)
@ -911,12 +936,12 @@ class Annulus(Circle):
self,
inner_radius: float | None = 1,
outer_radius: float | None = 2,
fill_opacity=1,
stroke_width=0,
color=WHITE,
mark_paths_closed=False,
fill_opacity: float = 1,
stroke_width: float = 0,
color: ParsableManimColor = WHITE,
mark_paths_closed: bool = False,
**kwargs,
):
) -> None:
self.mark_paths_closed = mark_paths_closed # is this even used?
self.inner_radius = inner_radius
self.outer_radius = outer_radius
@ -924,7 +949,7 @@ class Annulus(Circle):
fill_opacity=fill_opacity, stroke_width=stroke_width, color=color, **kwargs
)
def generate_points(self):
def generate_points(self) -> None:
self.radius = self.outer_radius
outer_circle = Circle(radius=self.outer_radius)
inner_circle = Circle(radius=self.inner_radius)
@ -959,7 +984,14 @@ class CubicBezier(VMobject, metaclass=ConvertToOpenGL):
"""
def __init__(self, start_anchor, start_handle, end_handle, end_anchor, **kwargs):
def __init__(
self,
start_anchor: CubicBezierPoints,
start_handle: CubicBezierPoints,
end_handle: CubicBezierPoints,
end_anchor: CubicBezierPoints,
**kwargs,
) -> None:
super().__init__(**kwargs)
self.add_cubic_bezier_curve(start_anchor, start_handle, end_handle, end_anchor)
@ -1045,12 +1077,12 @@ class ArcPolygon(VMobject, metaclass=ConvertToOpenGL):
def __init__(
self,
*vertices: list | np.ndarray,
*vertices: Point3D,
angle: float = PI / 4,
radius: float | None = None,
arc_config: list[dict] | None = None,
**kwargs,
):
) -> None:
n = len(vertices)
point_pairs = [(vertices[k], vertices[(k + 1) % n]) for k in range(n)]
@ -1188,7 +1220,7 @@ class ArcPolygonFromArcs(VMobject, metaclass=ConvertToOpenGL):
self.wait(2)
"""
def __init__(self, *arcs: Arc | ArcBetweenPoints, **kwargs):
def __init__(self, *arcs: Arc | ArcBetweenPoints, **kwargs) -> None:
if not all(isinstance(m, (Arc, ArcBetweenPoints)) for m in arcs):
raise ValueError(
"All ArcPolygon submobjects must be of type Arc/ArcBetweenPoints",
@ -1207,7 +1239,7 @@ class ArcPolygonFromArcs(VMobject, metaclass=ConvertToOpenGL):
self.append_points(arc1.points)
line = Line(arc1.get_end(), arc2.get_start())
len_ratio = line.get_length() / arc1.get_arc_length()
if math.isnan(len_ratio) or math.isinf(len_ratio):
if np.isnan(len_ratio) or np.isinf(len_ratio):
continue
line.insert_n_curves(int(arc1.get_num_curves() * len_ratio))
self.append_points(line.points)

View file

@ -2,8 +2,6 @@
from __future__ import annotations
import typing
import numpy as np
from pathops import Path as SkiaPath
from pathops import PathVerb, difference, intersection, union, xor
@ -11,6 +9,7 @@ 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
from ...constants import RendererType
@ -23,12 +22,9 @@ class _BooleanOps(VMobject, metaclass=ConvertToOpenGL):
objects (:class:`~.VMobject`).
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def _convert_2d_to_3d_array(
self,
points: typing.Iterable,
points: Point2D_Array,
z_dim: float = 0.0,
) -> list[np.ndarray]:
"""Converts an iterable with coordinates in 2d to 3d by adding
@ -43,7 +39,7 @@ class _BooleanOps(VMobject, metaclass=ConvertToOpenGL):
Returns
-------
typing.List[np.ndarray]
Point2D_Array
A list of array converted to 3d.
Example
@ -216,7 +212,7 @@ class Difference(_BooleanOps):
"""
def __init__(self, subject, clip, **kwargs) -> None:
def __init__(self, subject: VMobject, clip: VMobject, **kwargs) -> None:
super().__init__(**kwargs)
outpen = SkiaPath()
difference(
@ -258,7 +254,7 @@ class Intersection(_BooleanOps):
"""
def __init__(self, *vmobjects, **kwargs) -> None:
def __init__(self, *vmobjects: VMobject, **kwargs) -> None:
if len(vmobjects) < 2:
raise ValueError("At least 2 mobjects needed for Intersection.")
@ -311,7 +307,7 @@ class Exclusion(_BooleanOps):
"""
def __init__(self, subject, clip, **kwargs) -> None:
def __init__(self, subject: VMobject, clip: VMobject, **kwargs) -> None:
super().__init__(**kwargs)
outpen = SkiaPath()
xor(

View file

@ -14,9 +14,10 @@ __all__ = [
"RightAngle",
]
from typing import Any, Sequence
from typing import TYPE_CHECKING
import numpy as np
from typing_extensions import Self
from manim import config
from manim.constants import *
@ -26,19 +27,32 @@ from manim.mobject.mobject import Mobject
from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL
from manim.mobject.opengl.opengl_mobject import OpenGLMobject
from manim.mobject.types.vectorized_mobject import DashedVMobject, VGroup, VMobject
from manim.utils.color import *
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.utils.color import ParsableManimColor
from ..matrix import Matrix # Avoid circular import
class Line(TipableVMobject):
def __init__(self, start=LEFT, end=RIGHT, buff=0, path_arc=None, **kwargs):
def __init__(
self,
start: Point3D = LEFT,
end: Point3D = RIGHT,
buff: float = 0,
path_arc: float | None = None,
**kwargs,
) -> None:
self.dim = 3
self.buff = buff
self.path_arc = path_arc
self._set_start_and_end_attrs(start, end)
super().__init__(**kwargs)
def generate_points(self):
def generate_points(self) -> None:
self.set_points_by_ends(
start=self.start,
end=self.end,
@ -46,7 +60,13 @@ class Line(TipableVMobject):
path_arc=self.path_arc,
)
def set_points_by_ends(self, start, end, buff=0, path_arc=0):
def set_points_by_ends(
self,
start: Point3D,
end: Point3D,
buff: float = 0,
path_arc: float = 0,
) -> None:
if path_arc:
arc = ArcBetweenPoints(self.start, self.end, angle=self.path_arc)
self.set_points(arc.points)
@ -57,7 +77,7 @@ class Line(TipableVMobject):
init_points = generate_points
def _account_for_buff(self, buff):
def _account_for_buff(self, buff: float) -> Self:
if buff == 0:
return
#
@ -72,7 +92,7 @@ class Line(TipableVMobject):
self.pointwise_become_partial(self, buff_proportion, 1 - buff_proportion)
return self
def _set_start_and_end_attrs(self, start, end):
def _set_start_and_end_attrs(self, start: Point3D, end: Point3D) -> None:
# If either start or end are Mobjects, this
# gives their centers
rough_start = self._pointify(start)
@ -86,9 +106,9 @@ class Line(TipableVMobject):
def _pointify(
self,
mob_or_point: Mobject | Sequence[float],
direction: Sequence[float] | None = None,
) -> np.ndarray:
mob_or_point: Mobject | Point3D,
direction: Vector | None = None,
) -> Point3D:
"""Transforms a mobject into its corresponding point. Does nothing if a point is passed.
``direction`` determines the location of the point along its bounding box in that direction.
@ -108,11 +128,11 @@ class Line(TipableVMobject):
return mob.get_boundary_point(direction)
return np.array(mob_or_point)
def set_path_arc(self, new_value):
def set_path_arc(self, new_value: float) -> None:
self.path_arc = new_value
self.init_points()
def put_start_and_end_on(self, start: Sequence[float], end: Sequence[float]):
def put_start_and_end_on(self, start: Point3D, end: Point3D) -> Self:
"""Sets starts and end coordinates of a line.
Examples
@ -143,16 +163,16 @@ class Line(TipableVMobject):
self.generate_points()
return super().put_start_and_end_on(start, end)
def get_vector(self):
def get_vector(self) -> Vector:
return self.get_end() - self.get_start()
def get_unit_vector(self):
def get_unit_vector(self) -> Vector:
return normalize(self.get_vector())
def get_angle(self):
def get_angle(self) -> float:
return angle_of_vector(self.get_vector())
def get_projection(self, point: Sequence[float]) -> Sequence[float]:
def get_projection(self, point: Point3D) -> Vector:
"""Returns the projection of a point onto a line.
Parameters
@ -166,10 +186,10 @@ class Line(TipableVMobject):
unit_vect = normalize(end - start)
return start + np.dot(point - start, unit_vect) * unit_vect
def get_slope(self):
def get_slope(self) -> float:
return np.tan(self.get_angle())
def set_angle(self, angle, about_point=None):
def set_angle(self, angle: float, about_point: Point3D | None = None) -> Self:
if about_point is None:
about_point = self.get_start()
@ -180,7 +200,7 @@ class Line(TipableVMobject):
return self
def set_length(self, length):
def set_length(self, length: float) -> Self:
return self.scale(length / self.get_length())
@ -220,11 +240,11 @@ class DashedLine(Line):
def __init__(
self,
*args: Any,
*args,
dash_length: float = DEFAULT_DASH_LENGTH,
dashed_ratio: float = 0.5,
**kwargs,
):
) -> None:
self.dash_length = dash_length
self.dashed_ratio = dashed_ratio
super().__init__(*args, **kwargs)
@ -253,7 +273,7 @@ class DashedLine(Line):
int(np.ceil((self.get_length() / self.dash_length) * self.dashed_ratio)),
)
def get_start(self) -> np.ndarray:
def get_start(self) -> Point3D:
"""Returns the start point of the line.
Examples
@ -269,7 +289,7 @@ class DashedLine(Line):
else:
return super().get_start()
def get_end(self) -> np.ndarray:
def get_end(self) -> Point3D:
"""Returns the end point of the line.
Examples
@ -285,7 +305,7 @@ class DashedLine(Line):
else:
return super().get_end()
def get_first_handle(self) -> np.ndarray:
def get_first_handle(self) -> Point3D:
"""Returns the point of the first handle.
Examples
@ -298,7 +318,7 @@ class DashedLine(Line):
return self.submobjects[0].points[1]
def get_last_handle(self) -> np.ndarray:
def get_last_handle(self) -> Point3D:
"""Returns the point of the last handle.
Examples
@ -352,7 +372,7 @@ class TangentLine(Line):
length: float = 1,
d_alpha: float = 1e-6,
**kwargs,
):
) -> None:
self.length = length
self.d_alpha = d_alpha
da = self.d_alpha
@ -394,7 +414,7 @@ class Elbow(VMobject, metaclass=ConvertToOpenGL):
self.add(elbow_group)
"""
def __init__(self, width: float = 0.2, angle: float = 0, **kwargs):
def __init__(self, width: float = 0.2, angle: float = 0, **kwargs) -> None:
self.angle = angle
super().__init__(**kwargs)
self.set_points_as_corners([UP, UP + RIGHT, RIGHT])
@ -492,13 +512,13 @@ class Arrow(Line):
def __init__(
self,
*args: Any,
*args,
stroke_width: float = 6,
buff: float = MED_SMALL_BUFF,
max_tip_length_to_length_ratio: float = 0.25,
max_stroke_width_to_length_ratio: float = 5,
**kwargs,
):
) -> None:
self.max_tip_length_to_length_ratio = max_tip_length_to_length_ratio
self.max_stroke_width_to_length_ratio = max_stroke_width_to_length_ratio
tip_shape = kwargs.pop("tip_shape", ArrowTriangleFilledTip)
@ -509,7 +529,7 @@ class Arrow(Line):
self.add_tip(tip_shape=tip_shape)
self._set_stroke_width_from_length()
def scale(self, factor, scale_tips=False, **kwargs):
def scale(self, factor: float, scale_tips: bool = False, **kwargs) -> Self:
r"""Scale an arrow, but keep stroke width and arrow tip size fixed.
@ -559,7 +579,7 @@ class Arrow(Line):
self.add_tip(tip=old_tips[1], at_start=True)
return self
def get_normal_vector(self) -> np.ndarray:
def get_normal_vector(self) -> Vector:
"""Returns the normal of a vector.
Examples
@ -573,7 +593,7 @@ class Arrow(Line):
p0, p1, p2 = self.tip.get_start_anchors()[:3]
return normalize(np.cross(p2 - p1, p1 - p0))
def reset_normal_vector(self):
def reset_normal_vector(self) -> Self:
"""Resets the normal of a vector"""
self.normal_vector = self.get_normal_vector()
return self
@ -593,7 +613,7 @@ class Arrow(Line):
max_ratio = self.max_tip_length_to_length_ratio
return min(self.tip_length, max_ratio * self.get_length())
def _set_stroke_width_from_length(self):
def _set_stroke_width_from_length(self) -> Self:
"""Sets stroke width based on length."""
max_ratio = self.max_stroke_width_to_length_ratio
if config.renderer == RendererType.OPENGL:
@ -634,7 +654,7 @@ class Vector(Arrow):
self.add(plane, vector_1, vector_2)
"""
def __init__(self, direction: list | np.ndarray = RIGHT, buff: float = 0, **kwargs):
def __init__(self, direction: Vector = RIGHT, buff: float = 0, **kwargs) -> None:
self.buff = buff
if len(direction) == 2:
direction = np.hstack([direction, 0])
@ -647,7 +667,7 @@ class Vector(Arrow):
n_dim: int = 2,
color: ParsableManimColor | None = None,
**kwargs,
):
) -> Matrix:
"""Creates a label based on the coordinates of the vector.
Parameters
@ -750,7 +770,7 @@ class DoubleArrow(Arrow):
self.add(box, d1, d2, d3)
"""
def __init__(self, *args: Any, **kwargs):
def __init__(self, *args, **kwargs) -> None:
if "tip_shape_end" in kwargs:
kwargs["tip_shape"] = kwargs.pop("tip_shape_end")
tip_shape_start = kwargs.pop("tip_shape_start", ArrowTriangleFilledTip)
@ -871,8 +891,8 @@ class Angle(VMobject, metaclass=ConvertToOpenGL):
self,
line1: Line,
line2: Line,
radius: float = None,
quadrant: Sequence[int] = (1, 1),
radius: float | None = None,
quadrant: Point2D = (1, 1),
other_angle: bool = False,
dot: bool = False,
dot_radius: float | None = None,
@ -880,7 +900,7 @@ class Angle(VMobject, metaclass=ConvertToOpenGL):
dot_color: ParsableManimColor = WHITE,
elbow: bool = False,
**kwargs,
):
) -> None:
super().__init__(**kwargs)
self.lines = (line1, line2)
self.quadrant = quadrant
@ -1017,14 +1037,10 @@ class Angle(VMobject, metaclass=ConvertToOpenGL):
self.add(line1, line2, angle, value)
"""
if degrees:
return self.angle_value / DEGREES
return self.angle_value
return self.angle_value / DEGREES if degrees else self.angle_value
@staticmethod
def from_three_points(
A: np.ndarray, B: np.ndarray, C: np.ndarray, **kwargs
) -> Angle:
def from_three_points(A: Point3D, B: Point3D, C: Point3D, **kwargs) -> Angle:
"""The angle between the lines AB and BC.
This constructs the angle :math:`\\angle ABC`.
@ -1099,5 +1115,7 @@ class RightAngle(Angle):
self.add(plots)
"""
def __init__(self, line1: Line, line2: Line, length: float | None = None, **kwargs):
def __init__(
self, line1: Line, line2: Line, length: float | None = None, **kwargs
) -> None:
super().__init__(line1, line2, radius=length, elbow=True, **kwargs)

View file

@ -15,8 +15,9 @@ __all__ = [
"Cutout",
]
from math import ceil
from typing import Iterable, Sequence
from typing import TYPE_CHECKING
import numpy as np
@ -24,10 +25,16 @@ from manim.constants import *
from manim.mobject.geometry.arc import ArcBetweenPoints
from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL
from manim.mobject.types.vectorized_mobject import VGroup, VMobject
from manim.utils.color import *
from manim.utils.color import BLUE, WHITE, ParsableManimColor
from manim.utils.iterables import adjacent_n_tuples, adjacent_pairs
from manim.utils.space_ops import angle_between_vectors, normalize, regular_vertices
if TYPE_CHECKING:
from typing_extensions import Self
from manim.typing import Point3D, Point3D_Array
from manim.utils.color import ParsableManimColor
class Polygram(VMobject, metaclass=ConvertToOpenGL):
"""A generalized :class:`Polygon`, allowing for disconnected sets of edges.
@ -64,7 +71,9 @@ class Polygram(VMobject, metaclass=ConvertToOpenGL):
self.wait()
"""
def __init__(self, *vertex_groups: Iterable[Sequence[float]], color=BLUE, **kwargs):
def __init__(
self, *vertex_groups: Point3D, color: ParsableManimColor = BLUE, **kwargs
):
super().__init__(color=color, **kwargs)
for vertices in vertex_groups:
@ -76,7 +85,7 @@ class Polygram(VMobject, metaclass=ConvertToOpenGL):
[*(np.array(vertex) for vertex in vertices), first_vertex],
)
def get_vertices(self) -> np.ndarray:
def get_vertices(self) -> Point3D_Array:
"""Gets the vertices of the :class:`Polygram`.
Returns
@ -98,7 +107,7 @@ class Polygram(VMobject, metaclass=ConvertToOpenGL):
return self.get_start_anchors()
def get_vertex_groups(self) -> np.ndarray:
def get_vertex_groups(self) -> np.ndarray[Point3D_Array]:
"""Gets the vertex groups of the :class:`Polygram`.
Returns
@ -138,7 +147,7 @@ class Polygram(VMobject, metaclass=ConvertToOpenGL):
radius: float | list[float] = 0.5,
evenly_distribute_anchors: bool = False,
components_per_rounded_corner: int = 2,
):
) -> Self:
"""Rounds off the corners of the :class:`Polygram`.
Parameters
@ -303,7 +312,7 @@ class Polygon(Polygram):
self.add(isosceles, square_and_triangles)
"""
def __init__(self, *vertices: Sequence[float], **kwargs):
def __init__(self, *vertices: Point3D, **kwargs) -> None:
super().__init__(vertices, **kwargs)
@ -347,7 +356,7 @@ class RegularPolygram(Polygram):
radius: float = 1,
start_angle: float | None = None,
**kwargs,
):
) -> None:
# Regular polygrams can be expressed by the number of their vertices
# and their density. This relation can be expressed as its Schläfli
# symbol: {num_vertices/density}.
@ -423,7 +432,7 @@ class RegularPolygon(RegularPolygram):
self.add(poly_group)
"""
def __init__(self, n: int = 6, **kwargs):
def __init__(self, n: int = 6, **kwargs) -> None:
super().__init__(n, density=1, **kwargs)
@ -495,7 +504,7 @@ class Star(Polygon):
density: int = 2,
start_angle: float | None = TAU / 4,
**kwargs,
):
) -> None:
inner_angle = TAU / (2 * n)
if inner_radius is None:
@ -554,7 +563,7 @@ class Triangle(RegularPolygon):
self.add(tri_group)
"""
def __init__(self, **kwargs):
def __init__(self, **kwargs) -> None:
super().__init__(n=3, **kwargs)
@ -664,7 +673,7 @@ class Square(Rectangle):
self.add(square_1, square_2, square_3)
"""
def __init__(self, side_length: float = 2.0, **kwargs):
def __init__(self, side_length: float = 2.0, **kwargs) -> None:
self.side_length = side_length
super().__init__(height=side_length, width=side_length, **kwargs)
@ -734,7 +743,7 @@ class Cutout(VMobject, metaclass=ConvertToOpenGL):
self.wait()
"""
def __init__(self, main_shape: VMobject, *mobjects: VMobject, **kwargs):
def __init__(self, main_shape: VMobject, *mobjects: VMobject, **kwargs) -> None:
super().__init__(**kwargs)
self.append_points(main_shape.points)
if main_shape.get_direction() == "CW":

View file

@ -4,13 +4,17 @@ from __future__ import annotations
__all__ = ["SurroundingRectangle", "BackgroundRectangle", "Cross", "Underline"]
from typing import Any
from typing_extensions import Self
from manim import config, logger
from manim.constants import *
from manim.mobject.geometry.line import Line
from manim.mobject.geometry.polygram import RoundedRectangle
from manim.mobject.mobject import Mobject
from manim.mobject.types.vectorized_mobject import VGroup
from manim.utils.color import BLACK, RED, YELLOW, ParsableManimColor
from manim.utils.color import BLACK, RED, YELLOW, ManimColor, ParsableManimColor
class SurroundingRectangle(RoundedRectangle):
@ -38,8 +42,13 @@ class SurroundingRectangle(RoundedRectangle):
"""
def __init__(
self, mobject, color=YELLOW, buff=SMALL_BUFF, corner_radius=0.0, **kwargs
):
self,
mobject: Mobject,
color: ParsableManimColor = YELLOW,
buff: float = SMALL_BUFF,
corner_radius: float = 0.0,
**kwargs,
) -> None:
super().__init__(
color=color,
width=mobject.width + 2 * buff,
@ -78,7 +87,7 @@ class BackgroundRectangle(SurroundingRectangle):
def __init__(
self,
mobject,
mobject: Mobject,
color: ParsableManimColor | None = None,
stroke_width: float = 0,
stroke_opacity: float = 0,
@ -98,13 +107,13 @@ class BackgroundRectangle(SurroundingRectangle):
buff=buff,
**kwargs,
)
self.original_fill_opacity = self.fill_opacity
self.original_fill_opacity: float = self.fill_opacity
def pointwise_become_partial(self, mobject, a, b):
def pointwise_become_partial(self, mobject: Mobject, a: Any, b: float) -> Self:
self.set_fill(opacity=b * self.original_fill_opacity)
return self
def set_style(self, fill_opacity, **kwargs):
def set_style(self, fill_opacity: float, **kwargs) -> Self:
# Unchangeable style, except for fill_opacity
# All other style arguments are ignored
super().set_style(
@ -120,7 +129,7 @@ class BackgroundRectangle(SurroundingRectangle):
)
return self
def get_fill_color(self):
def get_fill_color(self) -> ManimColor:
return self.color
@ -153,10 +162,10 @@ class Cross(VGroup):
self,
mobject: Mobject | None = None,
stroke_color: ParsableManimColor = RED,
stroke_width: float = 6,
scale_factor: float = 1,
stroke_width: float = 6.0,
scale_factor: float = 1.0,
**kwargs,
):
) -> None:
super().__init__(
Line(UP + LEFT, DOWN + RIGHT), Line(UP + RIGHT, DOWN + LEFT), **kwargs
)
@ -181,7 +190,7 @@ class Underline(Line):
self.add(man, ul)
"""
def __init__(self, mobject, buff=SMALL_BUFF, **kwargs):
def __init__(self, mobject: Mobject, buff: float = SMALL_BUFF, **kwargs) -> None:
super().__init__(LEFT, RIGHT, buff=buff, **kwargs)
self.match_width(mobject)
self.next_to(mobject, DOWN, buff=self.buff)

View file

@ -13,6 +13,8 @@ __all__ = [
"StealthTip",
]
from typing import TYPE_CHECKING
import numpy as np
from manim.constants import *
@ -22,6 +24,9 @@ from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL
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
class ArrowTip(VMobject, metaclass=ConvertToOpenGL):
r"""Base class for arrow tips.
@ -106,11 +111,11 @@ class ArrowTip(VMobject, metaclass=ConvertToOpenGL):
self.add(*big_arrows, *small_arrows, *labels)
"""
def __init__(self, *args, **kwargs):
def __init__(self, *args, **kwargs) -> None:
raise NotImplementedError("Has to be implemented in inheriting subclasses.")
@property
def base(self):
def base(self) -> Point3D:
r"""The base point of the arrow tip.
This is the point connecting to the arrow line.
@ -128,7 +133,7 @@ class ArrowTip(VMobject, metaclass=ConvertToOpenGL):
return self.point_from_proportion(0.5)
@property
def tip_point(self):
def tip_point(self) -> Point3D:
r"""The tip point of the arrow tip.
Examples
@ -144,7 +149,7 @@ class ArrowTip(VMobject, metaclass=ConvertToOpenGL):
return self.points[0]
@property
def vector(self):
def vector(self) -> Vector:
r"""The vector pointing from the base point to the tip point.
Examples
@ -160,7 +165,7 @@ class ArrowTip(VMobject, metaclass=ConvertToOpenGL):
return self.tip_point - self.base
@property
def tip_angle(self):
def tip_angle(self) -> float:
r"""The angle of the arrow tip.
Examples
@ -176,7 +181,7 @@ class ArrowTip(VMobject, metaclass=ConvertToOpenGL):
return angle_of_vector(self.vector)
@property
def length(self):
def length(self) -> np.floating:
r"""The length of the arrow tip.
Examples
@ -238,13 +243,13 @@ class ArrowTriangleTip(ArrowTip, Triangle):
def __init__(
self,
fill_opacity=0,
stroke_width=3,
length=DEFAULT_ARROW_TIP_LENGTH,
width=DEFAULT_ARROW_TIP_LENGTH,
start_angle=PI,
fill_opacity: float = 0,
stroke_width: float = 3,
length: float = DEFAULT_ARROW_TIP_LENGTH,
width: float = DEFAULT_ARROW_TIP_LENGTH,
start_angle: float = PI,
**kwargs,
):
) -> None:
Triangle.__init__(
self,
fill_opacity=fill_opacity,
@ -264,7 +269,9 @@ class ArrowTriangleFilledTip(ArrowTriangleTip):
This is the default arrow tip shape.
"""
def __init__(self, fill_opacity=1, stroke_width=0, **kwargs):
def __init__(
self, fill_opacity: float = 1, stroke_width: float = 0, **kwargs
) -> None:
super().__init__(fill_opacity=fill_opacity, stroke_width=stroke_width, **kwargs)
@ -273,12 +280,12 @@ class ArrowCircleTip(ArrowTip, Circle):
def __init__(
self,
fill_opacity=0,
stroke_width=3,
length=DEFAULT_ARROW_TIP_LENGTH,
start_angle=PI,
fill_opacity: float = 0,
stroke_width: float = 3,
length: float = DEFAULT_ARROW_TIP_LENGTH,
start_angle: float = PI,
**kwargs,
):
) -> None:
self.start_angle = start_angle
Circle.__init__(
self, fill_opacity=fill_opacity, stroke_width=stroke_width, **kwargs
@ -290,7 +297,9 @@ class ArrowCircleTip(ArrowTip, Circle):
class ArrowCircleFilledTip(ArrowCircleTip):
r"""Circular arrow tip with filled tip."""
def __init__(self, fill_opacity=1, stroke_width=0, **kwargs):
def __init__(
self, fill_opacity: float = 1, stroke_width: float = 0, **kwargs
) -> None:
super().__init__(fill_opacity=fill_opacity, stroke_width=stroke_width, **kwargs)
@ -299,12 +308,12 @@ class ArrowSquareTip(ArrowTip, Square):
def __init__(
self,
fill_opacity=0,
stroke_width=3,
length=DEFAULT_ARROW_TIP_LENGTH,
start_angle=PI,
fill_opacity: float = 0,
stroke_width: float = 3,
length: float = DEFAULT_ARROW_TIP_LENGTH,
start_angle: float = PI,
**kwargs,
):
) -> None:
self.start_angle = start_angle
Square.__init__(
self,
@ -320,5 +329,7 @@ class ArrowSquareTip(ArrowTip, Square):
class ArrowSquareFilledTip(ArrowSquareTip):
r"""Square arrow tip with filled tip."""
def __init__(self, fill_opacity=1, stroke_width=0, **kwargs):
def __init__(
self, fill_opacity: float = 1, stroke_width: float = 0, **kwargs
) -> None:
super().__init__(fill_opacity=fill_opacity, stroke_width=stroke_width, **kwargs)

View file

@ -14,9 +14,10 @@ __all__ = [
import fractions as fr
import numbers
from typing import TYPE_CHECKING, Any, Callable, Iterable, Sequence
from typing import TYPE_CHECKING, Any, Callable, Iterable, Sequence, TypeVar, overload
import numpy as np
from typing_extensions import Self
from manim import config
from manim.constants import *
@ -54,6 +55,9 @@ 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
LineType = TypeVar("LineType", bound=Line)
class CoordinateSystem:
@ -108,12 +112,12 @@ class CoordinateSystem:
def __init__(
self,
x_range=None,
y_range=None,
x_length=None,
y_length=None,
dimension=2,
):
x_range: Sequence[float] | None = None,
y_range: Sequence[float] | None = None,
x_length: float | None = None,
y_length: float | None = None,
dimension: int = 2,
) -> None:
self.dimension = dimension
default_step = 1
@ -141,13 +145,13 @@ class CoordinateSystem:
self.y_length = y_length
self.num_sampled_graph_points_per_tick = 10
def coords_to_point(self, *coords):
def coords_to_point(self, *coords: Sequence[ManimFloat]):
raise NotImplementedError()
def point_to_coords(self, point):
def point_to_coords(self, point: Point3D):
raise NotImplementedError()
def polar_to_point(self, radius: float, azimuth: float) -> np.ndarray:
def polar_to_point(self, radius: float, azimuth: float) -> Point2D:
r"""Gets a point from polar coordinates.
Parameters
@ -178,7 +182,7 @@ class CoordinateSystem:
"""
return self.coords_to_point(radius * np.cos(azimuth), radius * np.sin(azimuth))
def point_to_polar(self, point: np.ndarray) -> tuple[float, float]:
def point_to_polar(self, point: np.ndarray) -> Point2D:
r"""Gets polar coordinates from a point.
Parameters
@ -194,11 +198,13 @@ class CoordinateSystem:
x, y = self.point_to_coords(point)
return np.sqrt(x**2 + y**2), np.arctan2(y, x)
def c2p(self, *coords):
def c2p(
self, *coords: float | Sequence[float] | Sequence[Sequence[float]] | np.ndarray
) -> np.ndarray:
"""Abbreviation for :meth:`coords_to_point`"""
return self.coords_to_point(*coords)
def p2c(self, point):
def p2c(self, point: Point3D):
"""Abbreviation for :meth:`point_to_coords`"""
return self.point_to_coords(point)
@ -213,7 +219,7 @@ class CoordinateSystem:
def get_axes(self):
raise NotImplementedError()
def get_axis(self, index):
def get_axis(self, index: int) -> Mobject:
return self.get_axes()[index]
def get_origin(self) -> np.ndarray:
@ -226,19 +232,19 @@ class CoordinateSystem:
"""
return self.coords_to_point(0, 0)
def get_x_axis(self):
def get_x_axis(self) -> Mobject:
return self.get_axis(0)
def get_y_axis(self):
def get_y_axis(self) -> Mobject:
return self.get_axis(1)
def get_z_axis(self):
def get_z_axis(self) -> Mobject:
return self.get_axis(2)
def get_x_unit_size(self):
def get_x_unit_size(self) -> float:
return self.get_x_axis().get_unit_size()
def get_y_unit_size(self):
def get_y_unit_size(self) -> float:
return self.get_y_axis().get_unit_size()
def get_x_axis_label(
@ -291,7 +297,7 @@ class CoordinateSystem:
direction: Sequence[float] = UP * 0.5 + RIGHT,
buff: float = SMALL_BUFF,
**kwargs,
):
) -> Mobject:
"""Generate a y-axis label.
Parameters
@ -370,9 +376,9 @@ class CoordinateSystem:
def add_coordinates(
self,
*axes_numbers: (Iterable[float] | None | dict[float, str | float | Mobject]),
**kwargs,
):
*axes_numbers: Iterable[float] | None | dict[float, str | float | Mobject],
**kwargs: Any,
) -> Self:
"""Adds labels to the axes. Use ``Axes.coordinate_labels`` to
access the coordinates after creation.
@ -426,15 +432,39 @@ class CoordinateSystem:
return self
# overload necessary until https://github.com/python/mypy/issues/3737 is supported
@overload
def get_line_from_axis_to_point(
self,
index: int,
point: Sequence[float],
line_func: Line = DashedLine,
line_config: dict | None = None,
color: ParsableManimColor | None = None,
stroke_width: float = 2,
) -> Line:
line_config: dict | None = ...,
color: ParsableManimColor | None = ...,
stroke_width: float = ...,
) -> DashedLine:
...
@overload
def get_line_from_axis_to_point(
self,
index: int,
point: Sequence[float],
line_func: type[LineType],
line_config: dict | None = ...,
color: ParsableManimColor | None = ...,
stroke_width: float = ...,
) -> LineType:
...
def get_line_from_axis_to_point( # type: ignore[no-untyped-def]
self,
index,
point,
line_func=DashedLine,
line_config=None,
color=None,
stroke_width=2,
):
"""Returns a straight line from a given axis to a point in the scene.
Parameters
@ -475,7 +505,7 @@ class CoordinateSystem:
line = line_func(axis.get_projection(point), point, **line_config)
return line
def get_vertical_line(self, point: Sequence[float], **kwargs) -> Line:
def get_vertical_line(self, point: Sequence[float], **kwargs: Any) -> Line:
"""A vertical line from the x-axis to a given point in the scene.
Parameters
@ -589,8 +619,8 @@ class CoordinateSystem:
function: Callable[[float], float],
x_range: Sequence[float] | None = None,
use_vectorized: bool = False,
**kwargs,
):
**kwargs: Any,
) -> ParametricFunction:
"""Generates a curve based on a function.
Parameters
@ -685,10 +715,10 @@ class CoordinateSystem:
def plot_implicit_curve(
self,
func: Callable,
func: Callable[[float, float], float],
min_depth: int = 5,
max_quads: int = 1500,
**kwargs,
**kwargs: Any,
) -> ImplicitFunction:
"""Creates the curves of an implicit function.
@ -737,7 +767,7 @@ class CoordinateSystem:
self,
function: Callable[[float], np.ndarray],
use_vectorized: bool = False,
**kwargs,
**kwargs: Any,
) -> ParametricFunction:
"""A parametric curve.
@ -784,8 +814,8 @@ class CoordinateSystem:
def plot_polar_graph(
self,
r_func: Callable[[float], float],
theta_range: Sequence[float] = [0, 2 * PI],
**kwargs,
theta_range: Sequence[float] | None = None,
**kwargs: Any,
) -> ParametricFunction:
"""A polar graph.
@ -811,6 +841,7 @@ class CoordinateSystem:
graph = plane.plot_polar_graph(r, [0, 2 * PI], color=ORANGE)
self.add(plane, graph)
"""
theta_range = theta_range if theta_range is not None else [0, 2 * PI]
graph = ParametricFunction(
function=lambda th: self.pr2pt(r_func(th), th),
t_range=theta_range,
@ -828,8 +859,8 @@ class CoordinateSystem:
| Sequence[tuple[ParsableManimColor, float]]
| None = None,
colorscale_axis: int = 2,
**kwargs,
):
**kwargs: Any,
) -> Surface | OpenGLSurface:
"""Generates a surface based on a function.
Parameters
@ -964,7 +995,9 @@ class CoordinateSystem:
f"x={x} not located in the range of the graph ([{self.p2c(graph.get_start())[0]}, {self.p2c(graph.get_end())[0]}])",
)
def input_to_graph_coords(self, x: float, graph: ParametricFunction) -> tuple:
def input_to_graph_coords(
self, x: float, graph: ParametricFunction
) -> tuple[float, float]:
"""Returns a tuple of the axis relative coordinates of the point
on the graph based on the x-value given.
@ -980,7 +1013,7 @@ class CoordinateSystem:
"""
return x, graph.underlying_function(x)
def i2gc(self, x: float, graph: ParametricFunction) -> tuple:
def i2gc(self, x: float, graph: ParametricFunction) -> tuple[float, float]:
"""Alias for :meth:`input_to_graph_coords`."""
return self.input_to_graph_coords(x, graph)
@ -997,7 +1030,7 @@ class CoordinateSystem:
buff: float = MED_SMALL_BUFF,
color: ParsableManimColor | None = None,
dot: bool = False,
dot_config: dict | None = None,
dot_config: dict[str, Any] | None = None,
) -> Mobject:
"""Creates a properly positioned label for the passed graph, with an optional dot.
@ -1242,8 +1275,8 @@ class CoordinateSystem:
color: ParsableManimColor | Iterable[ParsableManimColor] = (BLUE, GREEN),
opacity: float = 0.3,
bounded_graph: ParametricFunction = None,
**kwargs,
):
**kwargs: Any,
) -> Polygon:
"""Returns a :class:`~.Polygon` representing the area under the graph passed.
Parameters
@ -1359,7 +1392,9 @@ class CoordinateSystem:
p1 = np.array([*self.input_to_graph_coords(x + dx, graph)])
return angle_of_vector(p1 - p0)
def slope_of_tangent(self, x: float, graph: ParametricFunction, **kwargs) -> float:
def slope_of_tangent(
self, x: float, graph: ParametricFunction, **kwargs: Any
) -> float:
"""Returns the slope of the tangent to the plotted curve
at a particular x-value.
@ -1437,8 +1472,8 @@ class CoordinateSystem:
y_intercept: float = 0,
samples: int = 50,
use_vectorized: bool = False,
**kwargs,
):
**kwargs: Any,
) -> ParametricFunction:
"""Plots an antiderivative graph.
Parameters
@ -1619,7 +1654,7 @@ class CoordinateSystem:
graph: ParametricFunction,
x_range: Sequence[float] | None = None,
num_lines: int = 20,
**kwargs,
**kwargs: Any,
) -> VGroup:
"""Obtains multiple lines from the x-axis to the curve.
@ -1678,7 +1713,7 @@ class CoordinateSystem:
label_color: ParsableManimColor | None = None,
triangle_size: float = MED_SMALL_BUFF,
triangle_color: ParsableManimColor | None = WHITE,
line_func: Line = Line,
line_func: type[Line] = Line,
line_color: ParsableManimColor = YELLOW,
) -> VGroup:
"""Creates a labelled triangle marker with a vertical line from the x-axis
@ -1811,8 +1846,8 @@ class Axes(VGroup, CoordinateSystem, metaclass=ConvertToOpenGL):
x_axis_config: dict | None = None,
y_axis_config: dict | None = None,
tips: bool = True,
**kwargs,
):
**kwargs: Any,
) -> None:
VGroup.__init__(self, **kwargs)
CoordinateSystem.__init__(self, x_range, y_range, x_length, y_length)
@ -1879,7 +1914,7 @@ class Axes(VGroup, CoordinateSystem, metaclass=ConvertToOpenGL):
@staticmethod
def _update_default_configs(
default_configs: tuple[dict[Any, Any]], passed_configs: tuple[dict[Any, Any]]
):
) -> None:
"""Takes in two tuples of dicts and return modifies the first such that values from
``passed_configs`` overwrite values in ``default_configs``. If a key does not exist
in default_configs, it is added to the dict.
@ -1912,7 +1947,7 @@ class Axes(VGroup, CoordinateSystem, metaclass=ConvertToOpenGL):
def _create_axis(
self,
range_terms: Sequence[float],
axis_config: dict,
axis_config: dict[str, Any],
length: float,
) -> NumberLine:
"""Creates an axis and dynamically adjusts its position depending on where 0 is located on the line.
@ -2161,8 +2196,8 @@ class Axes(VGroup, CoordinateSystem, metaclass=ConvertToOpenGL):
line_color: ParsableManimColor = YELLOW,
add_vertex_dots: bool = True,
vertex_dot_radius: float = DEFAULT_DOT_RADIUS,
vertex_dot_style: dict | None = None,
**kwargs,
vertex_dot_style: dict[str, Any] | None = None,
**kwargs: Any,
) -> VDict:
"""Draws a line graph.
@ -2304,15 +2339,15 @@ class ThreeDAxes(Axes):
x_length: float | None = config.frame_height + 2.5,
y_length: float | None = config.frame_height + 2.5,
z_length: float | None = config.frame_height - 1.5,
z_axis_config: dict | None = None,
z_normal: Sequence[float] = DOWN,
z_axis_config: dict[str, Any] | None = None,
z_normal: Vector3 = DOWN,
num_axis_pieces: int = 20,
light_source: Sequence[float] = 9 * DOWN + 7 * LEFT + 10 * OUT,
# opengl stuff (?)
depth=None,
gloss=0.5,
**kwargs,
):
**kwargs: dict[str, Any],
) -> None:
super().__init__(
x_range=x_range,
x_length=x_length,
@ -2368,14 +2403,14 @@ class ThreeDAxes(Axes):
self._add_3d_pieces()
self._set_axis_shading()
def _add_3d_pieces(self):
def _add_3d_pieces(self) -> None:
for axis in self.axes:
axis.pieces = VGroup(*axis.get_pieces(self.num_axis_pieces))
axis.add(axis.pieces)
axis.set_stroke(width=0, family=False)
axis.set_shade_in_3d(True)
def _set_axis_shading(self):
def _set_axis_shading(self) -> None:
def make_func(axis):
vect = self.light_source
return lambda: (
@ -2395,8 +2430,8 @@ class ThreeDAxes(Axes):
edge: Sequence[float] = UR,
direction: Sequence[float] = UR,
buff: float = SMALL_BUFF,
rotation=PI / 2,
rotation_axis=OUT,
rotation: float = PI / 2,
rotation_axis: Vector3 = OUT,
**kwargs,
) -> Mobject:
"""Generate a y-axis label.
@ -2443,12 +2478,12 @@ class ThreeDAxes(Axes):
def get_z_axis_label(
self,
label: float | str | Mobject,
edge: Sequence[float] = OUT,
direction: Sequence[float] = RIGHT,
edge: Vector3 = OUT,
direction: Vector3 = RIGHT,
buff: float = SMALL_BUFF,
rotation=PI / 2,
rotation_axis=RIGHT,
**kwargs,
rotation: float = PI / 2,
rotation_axis: Vector3 = RIGHT,
**kwargs: Any,
) -> Mobject:
"""Generate a z-axis label.
@ -2630,11 +2665,11 @@ class NumberPlane(Axes):
),
x_length: float | None = None,
y_length: float | None = None,
background_line_style: dict | None = None,
faded_line_style: dict | None = None,
background_line_style: dict[str, Any] | None = None,
faded_line_style: dict[str, Any] | None = None,
faded_line_ratio: int = 1,
make_smooth_after_applying_functions: bool = True,
**kwargs,
**kwargs: dict[str, Any],
):
# configs
self.axis_config = {
@ -2679,7 +2714,7 @@ class NumberPlane(Axes):
self._init_background_lines()
def _init_background_lines(self):
def _init_background_lines(self) -> None:
"""Will init all the lines of NumberPlanes (faded or not)"""
if self.faded_line_style is None:
style = dict(self.background_line_style)
@ -2800,13 +2835,13 @@ class NumberPlane(Axes):
lines2.add(new_line)
return lines1, lines2
def get_vector(self, coords: Sequence[float], **kwargs):
def get_vector(self, coords: Sequence[ManimFloat], **kwargs: Any) -> Arrow:
kwargs["buff"] = 0
return Arrow(
self.coords_to_point(0, 0), self.coords_to_point(*coords), **kwargs
)
def prepare_for_nonlinear_transform(self, num_inserted_curves: int = 50):
def prepare_for_nonlinear_transform(self, num_inserted_curves: int = 50) -> Self:
for mob in self.family_members_with_points():
num_curves = mob.get_num_curves()
if num_inserted_curves > num_curves:
@ -2900,13 +2935,13 @@ class PolarPlane(Axes):
azimuth_direction: str = "CCW",
azimuth_label_buff: float = SMALL_BUFF,
azimuth_label_font_size: float = 24,
radius_config: dict | None = None,
background_line_style: dict | None = None,
faded_line_style: dict | None = None,
radius_config: dict[str, Any] | None = None,
background_line_style: dict[str, Any] | None = None,
faded_line_style: dict[str, Any] | None = None,
faded_line_ratio: int = 1,
make_smooth_after_applying_functions: bool = True,
**kwargs,
):
**kwargs: Any,
) -> None:
# error catching
if azimuth_units in ["PI radians", "TAU radians", "degrees", "gradians", None]:
self.azimuth_units = azimuth_units
@ -2977,7 +3012,7 @@ class PolarPlane(Axes):
self._init_background_lines()
def _init_background_lines(self):
def _init_background_lines(self) -> None:
"""Will init all the lines of NumberPlanes (faded or not)"""
if self.faded_line_style is None:
style = dict(self.background_line_style)
@ -3057,13 +3092,13 @@ class PolarPlane(Axes):
"""
return self.axes
def get_vector(self, coords, **kwargs):
def get_vector(self, coords: Sequence[ManimFloat], **kwargs: Any) -> Arrow:
kwargs["buff"] = 0
return Arrow(
self.coords_to_point(0, 0), self.coords_to_point(*coords), **kwargs
)
def prepare_for_nonlinear_transform(self, num_inserted_curves=50):
def prepare_for_nonlinear_transform(self, num_inserted_curves: int = 50) -> Self:
for mob in self.family_members_with_points():
num_curves = mob.get_num_curves()
if num_inserted_curves > num_curves:
@ -3074,7 +3109,7 @@ class PolarPlane(Axes):
self,
r_values: Iterable[float] | None = None,
a_values: Iterable[float] | None = None,
**kwargs,
**kwargs: Any,
) -> VDict:
"""Gets labels for the coordinates
@ -3176,7 +3211,7 @@ class PolarPlane(Axes):
self,
r_values: Iterable[float] | None = None,
a_values: Iterable[float] | None = None,
):
) -> Self:
"""Adds the coordinates.
Parameters
@ -3189,7 +3224,7 @@ class PolarPlane(Axes):
self.add(self.get_coordinate_labels(r_values, a_values))
return self
def get_radian_label(self, number, font_size=24, **kwargs):
def get_radian_label(self, number, font_size: float = 24, **kwargs: Any) -> MathTex:
constant_label = {"PI radians": r"\pi", "TAU radians": r"\tau"}[
self.azimuth_units
]
@ -3258,7 +3293,7 @@ class ComplexPlane(NumberPlane):
"""
def __init__(self, **kwargs):
def __init__(self, **kwargs: Any) -> None:
super().__init__(
**kwargs,
)
@ -3284,7 +3319,7 @@ class ComplexPlane(NumberPlane):
"""Abbreviation for :meth:`number_to_point`."""
return self.number_to_point(number)
def point_to_number(self, point: Sequence[float]) -> complex:
def point_to_number(self, point: Point3D) -> complex:
"""Accepts a point and returns a complex number equivalent to that point on the plane.
Parameters
@ -3301,7 +3336,7 @@ class ComplexPlane(NumberPlane):
x, y = self.point_to_coords(point)
return complex(x, y)
def p2n(self, point: Sequence[float]) -> complex:
def p2n(self, point: Point3D) -> complex:
"""Abbreviation for :meth:`point_to_number`."""
return self.point_to_number(point)
@ -3319,7 +3354,7 @@ class ComplexPlane(NumberPlane):
return [*x_numbers, *y_numbers]
def get_coordinate_labels(
self, *numbers: Iterable[float | complex], **kwargs
self, *numbers: Iterable[float | complex], **kwargs: Any
) -> VGroup:
"""Generates the :class:`~.DecimalNumber` mobjects for the coordinates of the plane.
@ -3354,7 +3389,9 @@ class ComplexPlane(NumberPlane):
self.coordinate_labels.add(number_mob)
return self.coordinate_labels
def add_coordinates(self, *numbers: Iterable[float | complex], **kwargs):
def add_coordinates(
self, *numbers: Iterable[float | complex], **kwargs: Any
) -> Self:
"""Adds the labels produced from :meth:`~.NumberPlane.get_coordinate_labels` to the plane.
Parameters

File diff suppressed because it is too large Load diff

View file

@ -13,8 +13,16 @@ import numpy as np
from manim import config, logger
from manim.constants import *
from manim.renderer.shader_wrapper import get_colormap_code
from manim.utils.bezier import integer_interpolate, interpolate
from manim.utils.color import *
from manim.utils.color import (
WHITE,
ManimColor,
ParsableManimColor,
color_gradient,
color_to_rgb,
rgb_to_hex,
)
from manim.utils.config_ops import _Data, _Uniforms
# from ..utils.iterables import batch_by_property

View file

@ -3,7 +3,7 @@ from __future__ import annotations
import itertools as it
import operator as op
from functools import reduce, wraps
from typing import Callable, Iterable, Optional, Sequence
from typing import Callable, Iterable, Sequence
import moderngl
import numpy as np
@ -22,7 +22,7 @@ from manim.utils.bezier import (
proportions_along_bezier_curve_for_point,
quadratic_bezier_remap,
)
from manim.utils.color import *
from manim.utils.color import BLACK, WHITE, ManimColor, ParsableManimColor
from manim.utils.config_ops import _Data
from manim.utils.iterables import listify, make_even, resize_with_interpolation
from manim.utils.space_ops import (
@ -136,6 +136,7 @@ class OpenGLVMobject(OpenGLMobject):
self.needs_new_triangulation = True
self.triangulation = np.zeros(0, dtype="i4")
self.orientation = 1
self.fill_data = None
self.stroke_data = None
self.fill_shader_wrapper = None
@ -1283,14 +1284,17 @@ class OpenGLVMobject(OpenGLMobject):
for _ in range(-diff):
ipc[np.argmax(ipc)] -= 1
new_points = []
new_length = sum(x + 1 for x in ipc)
new_points = np.empty((new_length, nppc, 3))
i = 0
for group, n_inserts in zip(bezier_groups, ipc):
# What was once a single quadratic curve defined
# by "group" will now be broken into n_inserts + 1
# smaller quadratic curves
alphas = np.linspace(0, 1, n_inserts + 2)
for a1, a2 in zip(alphas, alphas[1:]):
new_points += partial_quadratic_bezier_points(group, a1, a2)
new_points[i] = partial_quadratic_bezier_points(group, a1, a2)
i = i + 1
return np.vstack(new_points)
def interpolate(self, mobject1, mobject2, alpha, *args, **kwargs):

View file

@ -28,7 +28,7 @@ import operator as op
import re
from functools import reduce
from textwrap import dedent
from typing import Dict, Iterable, Optional
from typing import Iterable
from manim import config, logger
from manim.constants import *

View file

@ -462,7 +462,7 @@ class Text(SVGMobject):
t2g = kwargs.pop("text2gradient", t2g)
t2s = kwargs.pop("text2slant", t2s)
t2w = kwargs.pop("text2weight", t2w)
self.t2c = t2c
self.t2c = {k: ManimColor(v).to_hex() for k, v in t2c.items()}
self.t2f = t2f
self.t2g = t2g
self.t2s = t2s
@ -482,7 +482,7 @@ class Text(SVGMobject):
self.line_spacing = self._font_size + self._font_size * self.line_spacing
color: ManimColor = ManimColor(color) if color else VMobject().color
file_name = self._text2svg(color)
file_name = self._text2svg(color.to_hex())
PangoUtils.remove_last_M(file_name)
super().__init__(
file_name,
@ -737,7 +737,7 @@ class Text(SVGMobject):
# setting_args requires values to be strings
default_args = {
arg: getattr(self, arg) if arg != "color" else str(color) for _, arg in t2xs
arg: getattr(self, arg) if arg != "color" else color for _, arg in t2xs
}
settings = self._get_settings_from_t2xs(t2xs, default_args)
@ -1307,7 +1307,7 @@ class MarkupText(SVGMobject):
else:
self.scale(font_val / self.font_size)
def _text2hash(self, color: ManimColor):
def _text2hash(self, color: ParsableManimColor):
"""Generates ``sha256`` hash for file name."""
settings = (
"MARKUPPANGO"
@ -1324,8 +1324,9 @@ class MarkupText(SVGMobject):
hasher.update(id_str.encode())
return hasher.hexdigest()[:16]
def _text2svg(self, color: ManimColor):
def _text2svg(self, color: ParsableManimColor | None):
"""Convert the text to SVG using Pango."""
color = ManimColor(color)
size = self._font_size
line_spacing = self.line_spacing
size /= TEXT2SVG_ADJUSTMENT_FACTOR

View file

@ -14,40 +14,45 @@ __all__ = [
]
from typing import TYPE_CHECKING, Literal
import numpy as np
from manim.constants import ORIGIN, UP
from manim.utils.space_ops import get_unit_normal
if TYPE_CHECKING:
from manim.typing import Point3D, Vector
def get_3d_vmob_gradient_start_and_end_points(vmob):
def get_3d_vmob_gradient_start_and_end_points(vmob) -> tuple[Point3D, Point3D]:
return (
get_3d_vmob_start_corner(vmob),
get_3d_vmob_end_corner(vmob),
)
def get_3d_vmob_start_corner_index(vmob):
def get_3d_vmob_start_corner_index(vmob) -> Literal[0]:
return 0
def get_3d_vmob_end_corner_index(vmob):
def get_3d_vmob_end_corner_index(vmob) -> int:
return ((len(vmob.points) - 1) // 6) * 3
def get_3d_vmob_start_corner(vmob):
def get_3d_vmob_start_corner(vmob) -> Point3D:
if vmob.get_num_points() == 0:
return np.array(ORIGIN)
return vmob.points[get_3d_vmob_start_corner_index(vmob)]
def get_3d_vmob_end_corner(vmob):
def get_3d_vmob_end_corner(vmob) -> Point3D:
if vmob.get_num_points() == 0:
return np.array(ORIGIN)
return vmob.points[get_3d_vmob_end_corner_index(vmob)]
def get_3d_vmob_unit_normal(vmob, point_index):
def get_3d_vmob_unit_normal(vmob, point_index: int) -> Vector:
n_points = vmob.get_num_points()
if len(vmob.get_anchors()) <= 2:
return np.array(UP)
@ -63,9 +68,9 @@ def get_3d_vmob_unit_normal(vmob, point_index):
return unit_normal
def get_3d_vmob_start_corner_unit_normal(vmob):
def get_3d_vmob_start_corner_unit_normal(vmob) -> Vector:
return get_3d_vmob_unit_normal(vmob, get_3d_vmob_start_corner_index(vmob))
def get_3d_vmob_end_corner_unit_normal(vmob):
def get_3d_vmob_end_corner_unit_normal(vmob) -> Vector:
return get_3d_vmob_unit_normal(vmob, get_3d_vmob_end_corner_index(vmob))

View file

@ -2,6 +2,9 @@
from __future__ import annotations
from manim.typing import Point3D, Vector3
from manim.utils.color import BLUE, BLUE_D, BLUE_E, LIGHT_GREY, WHITE, interpolate_color
__all__ = [
"ThreeDVMobject",
"Surface",
@ -16,9 +19,10 @@ __all__ = [
"Torus",
]
from typing import Callable, Sequence
from typing import Any, Callable, Iterable, Sequence
import numpy as np
from typing_extensions import Self
from manim import config, logger
from manim.constants import *
@ -113,7 +117,7 @@ class Surface(VGroup, metaclass=ConvertToOpenGL):
stroke_width: float = 0.5,
should_make_jagged: bool = False,
pre_function_handle_to_anchor_scale_factor: float = 0.00001,
**kwargs,
**kwargs: Any,
) -> None:
self.u_range = u_range
self.v_range = v_range
@ -141,16 +145,9 @@ class Surface(VGroup, metaclass=ConvertToOpenGL):
self.make_jagged()
def func(self, u: float, v: float) -> np.ndarray:
"""The z values defining the :class:`Surface` being plotted.
Returns
-------
:class:`numpy.array`
The z values defining the :class:`Surface`.
"""
return self._func(u, v)
def _get_u_values_and_v_values(self):
def _get_u_values_and_v_values(self) -> tuple[np.ndarray, np.ndarray]:
res = tuplify(self.resolution)
if len(res) == 1:
u_res = v_res = res[0]
@ -162,7 +159,7 @@ class Surface(VGroup, metaclass=ConvertToOpenGL):
return u_values, v_values
def _setup_in_uv_space(self):
def _setup_in_uv_space(self) -> None:
u_values, v_values = self._get_u_values_and_v_values()
faces = VGroup()
for i in range(len(u_values) - 1):
@ -197,8 +194,8 @@ class Surface(VGroup, metaclass=ConvertToOpenGL):
self.set_fill_by_checkerboard(*self.checkerboard_colors)
def set_fill_by_checkerboard(
self, *colors: Sequence[ParsableManimColor], opacity: float = None
) -> Mobject:
self, *colors: Iterable[ParsableManimColor], opacity: float | None = None
) -> Self:
"""Sets the fill_color of each face of :class:`Surface` in
an alternating pattern.
@ -227,7 +224,7 @@ class Surface(VGroup, metaclass=ConvertToOpenGL):
colorscale: list[ParsableManimColor] | ParsableManimColor | None = None,
axis: int = 2,
**kwargs,
) -> Mobject:
) -> Self:
"""Sets the color of each mobject of a parametric surface to a color
relative to its axis-value.
@ -381,9 +378,9 @@ class Sphere(Surface):
def __init__(
self,
center: Sequence[float] = ORIGIN,
center: Point3D = ORIGIN,
radius: float = 1,
resolution: Sequence[int] = None,
resolution: Sequence[int] | None = None,
u_range: Sequence[float] = (0, TAU),
v_range: Sequence[float] = (0, PI),
**kwargs,
@ -459,7 +456,7 @@ class Dot3D(Sphere):
point: list | np.ndarray = ORIGIN,
radius: float = DEFAULT_DOT_RADIUS,
color: ParsableManimColor = WHITE,
resolution=(8, 8),
resolution: tuple[int, int] = (8, 8),
**kwargs,
) -> None:
super().__init__(center=point, radius=radius, resolution=resolution, **kwargs)
@ -551,7 +548,9 @@ class Prism(Cube):
self.add(prismSmall, prismLarge)
"""
def __init__(self, dimensions: Sequence[int] = [3, 2, 1], **kwargs) -> None:
def __init__(
self, dimensions: tuple[float, float, float] | np.ndarray = [3, 2, 1], **kwargs
) -> None:
self.dimensions = dimensions
super().__init__(**kwargs)
@ -609,8 +608,8 @@ class Cone(Surface):
v_range: Sequence[float] = [0, TAU],
u_min: float = 0,
checkerboard_colors: bool = False,
**kwargs,
):
**kwargs: Any,
) -> None:
self.direction = direction
self.theta = PI - np.arctan(base_radius / height)
@ -662,7 +661,7 @@ class Cone(Surface):
],
)
def _rotate_to_direction(self):
def _rotate_to_direction(self) -> None:
x, y, z = self.direction
r = np.sqrt(x**2 + y**2 + z**2)
@ -821,7 +820,7 @@ class Cylinder(Surface):
self.base_bottom.shift(self.u_range[0] * IN)
self.add(self.base_top, self.base_bottom)
def _rotate_to_direction(self):
def _rotate_to_direction(self) -> None:
x, y, z = self.direction
r = np.sqrt(x**2 + y**2 + z**2)
@ -910,7 +909,7 @@ class Line3D(Cylinder):
start: np.ndarray = LEFT,
end: np.ndarray = RIGHT,
thickness: float = 0.02,
color: ParsableManimColor = None,
color: ParsableManimColor | None = None,
**kwargs,
):
self.thickness = thickness
@ -952,7 +951,9 @@ class Line3D(Cylinder):
self.shift((self.start + self.end) / 2)
def pointify(
self, mob_or_point: Mobject | float, direction: np.ndarray = None
self,
mob_or_point: Mobject | Point3D,
direction: Vector3 = None,
) -> np.ndarray:
"""Gets a point representing the center of the :class:`Mobjects <.Mobject>`.
@ -998,7 +999,11 @@ class Line3D(Cylinder):
@classmethod
def parallel_to(
cls, line: Line3D, point: Sequence[float] = ORIGIN, length: float = 5, **kwargs
cls,
line: Line3D,
point: Vector3 = ORIGIN,
length: float = 5,
**kwargs,
) -> Line3D:
"""Returns a line parallel to another line going through
a given point.
@ -1042,7 +1047,11 @@ class Line3D(Cylinder):
@classmethod
def perpendicular_to(
cls, line: Line3D, point: Sequence[float] = ORIGIN, length: float = 5, **kwargs
cls,
line: Line3D,
point: Vector3 = ORIGIN,
length: float = 5,
**kwargs,
) -> Line3D:
"""Returns a line perpendicular to another line going through
a given point.
@ -1191,7 +1200,7 @@ class Torus(Surface):
minor_radius: float = 1,
u_range: Sequence[float] = (0, TAU),
v_range: Sequence[float] = (0, TAU),
resolution: Sequence[int] = None,
resolution: tuple[int, int] | None = None,
**kwargs,
) -> None:
if config.renderer == RendererType.OPENGL:

File diff suppressed because it is too large Load diff

133
manim/typing.py Normal file
View file

@ -0,0 +1,133 @@
from __future__ import annotations
from os import PathLike
from typing import Callable, Tuple, Union
import numpy as np
import numpy.typing as npt
from typing_extensions import TypeAlias
# Color Types
ManimFloat: TypeAlias = np.float64
ManimInt: TypeAlias = np.int64
ManimColorDType: TypeAlias = ManimFloat
RGB_Array_Float: TypeAlias = npt.NDArray[ManimFloat]
RGB_Tuple_Float: TypeAlias = Tuple[float, float, float]
RGB_Array_Int: TypeAlias = npt.NDArray[ManimInt]
RGB_Tuple_Int: TypeAlias = Tuple[int, int, int]
RGBA_Array_Float: TypeAlias = npt.NDArray[ManimFloat]
RGBA_Tuple_Float: TypeAlias = Tuple[float, float, float, float]
RGBA_Array_Int: TypeAlias = npt.NDArray[ManimInt]
RGBA_Tuple_Int: TypeAlias = Tuple[int, int, int, int]
HSV_Array_Float: TypeAlias = RGB_Array_Float
HSV_Tuple_Float: TypeAlias = RGB_Tuple_Float
ManimColorInternal: TypeAlias = npt.NDArray[ManimColorDType]
# Point Types
PointDType: TypeAlias = ManimFloat
""" DType for all points. """
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.
"""
Point2D: TypeAlias = Union[InternalPoint2D, Tuple[float, float]]
""" `shape: (2,)` A 2D point. `[float, float]`. """
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.
"""
Point3D: TypeAlias = Union[InternalPoint3D, Tuple[float, float, float]]
""" `shape: (3,)` A 3D point. `[float, float, float]` """
# 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)
"""
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.
"""
Point3D_Array: TypeAlias = Union[
InternalPoint3D_Array, Tuple[Tuple[float, float, float], ...]
]
""" `shape: (N,3)` An Array of Points in 3D Space `[[float, float, float], ...]`.
(Please refer to the documentation of the function you are using for further type Information)
"""
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)
"""
# Vector Types
Vector3: TypeAlias = npt.NDArray[PointDType]
""" `shape: (3,)` A Vector `[float, float, float]`. """
Vector: TypeAlias = npt.NDArray[PointDType]
""" `shape: (N,)` A Vector `[float, ...]`. """
RowVector: TypeAlias = npt.NDArray[PointDType]
""" `shape: (1,N)` A Row Vector `[[float, ...]]`. """
ColVector: TypeAlias = npt.NDArray[PointDType]
""" `shape: (N,1)` A Column Vector `[[float], [float], ...]`. """
MatrixMN: TypeAlias = npt.NDArray[PointDType]
""" `shape: (M,N)` A Matrix `[[float, ...], [float, ...], ...]`. """
Zeros: TypeAlias = npt.NDArray[ManimFloat]
"""A Matrix of Zeros. Typically created with `numpy.zeros((M,N))`"""
# 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."""
# Misc
PathFuncType: TypeAlias = Callable[[Point3D, Point3D, float], Point3D]
"""Function mapping two points and an alpha value to a new point"""
MappingFunction: TypeAlias = Callable[[Point3D], 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]"

View file

@ -2,6 +2,17 @@
from __future__ import annotations
from manim.typing import (
BezierPoints,
ColVector,
MatrixMN,
Point3D,
Point3D_Array,
PointDType,
QuadraticBezierPoints,
QuadraticBezierPoints_Array,
)
__all__ = [
"bezier",
"partial_bezier_points",
@ -20,11 +31,11 @@ __all__ = [
]
import typing
from functools import reduce
from typing import Iterable
from typing import Any, Callable, Sequence, overload
import numpy as np
import numpy.typing as npt
from scipy import linalg
from ..utils.simple_functions import choose
@ -32,8 +43,8 @@ from ..utils.space_ops import cross2d, find_intersection
def bezier(
points: np.ndarray,
) -> typing.Callable[[float], int | typing.Iterable]:
points: Sequence[Point3D] | Point3D_Array,
) -> Callable[[float], Point3D]:
"""Classic implementation of a bezier curve.
Parameters
@ -43,34 +54,39 @@ def bezier(
Returns
-------
typing.Callable[[float], typing.Union[int, typing.Iterable]]
function describing the bezier curve.
You can pass a t value between 0 and 1 to get the corresponding point on the curve.
"""
n = len(points) - 1
# Cubic Bezier curve
if n == 3:
return (
lambda t: (1 - t) ** 3 * points[0]
return lambda t: np.asarray(
(1 - t) ** 3 * points[0]
+ 3 * t * (1 - t) ** 2 * points[1]
+ 3 * (1 - t) * t**2 * points[2]
+ t**3 * points[3]
+ t**3 * points[3],
dtype=PointDType,
)
# Quadratic Bezier curve
if n == 2:
return (
lambda t: (1 - t) ** 2 * points[0]
+ 2 * t * (1 - t) * points[1]
+ t**2 * points[2]
return lambda t: np.asarray(
(1 - t) ** 2 * points[0] + 2 * t * (1 - t) * points[1] + t**2 * points[2],
dtype=PointDType,
)
return lambda t: sum(
((1 - t) ** (n - k)) * (t**k) * choose(n, k) * point
for k, point in enumerate(points)
return lambda t: np.asarray(
np.asarray(
[
(((1 - t) ** (n - k)) * (t**k) * choose(n, k) * point)
for k, point in enumerate(points)
],
dtype=PointDType,
).sum(axis=0)
)
def partial_bezier_points(points: np.ndarray, a: float, b: float) -> np.ndarray:
# !TODO: This function has still a weird implementation with the overlapping points
def partial_bezier_points(points: BezierPoints, a: float, b: float) -> BezierPoints:
"""Given an array of points which define bezier curve, and two numbers 0<=a<b<=1, return an array of the same size,
which describes the portion of the original bezier curve on the interval [a, b].
@ -90,23 +106,31 @@ def partial_bezier_points(points: np.ndarray, a: float, b: float) -> np.ndarray:
np.ndarray
Set of points defining the partial bezier curve.
"""
_len = len(points)
if a == 1:
return [points[-1]] * len(points)
return np.asarray([points[-1]] * _len, dtype=PointDType)
a_to_1 = np.array([bezier(points[i:])(a) for i in range(len(points))])
a_to_1 = np.asarray(
[bezier(points[i:])(a) for i in range(_len)],
dtype=PointDType,
)
end_prop = (b - a) / (1.0 - a)
return np.array([bezier(a_to_1[: i + 1])(end_prop) for i in range(len(points))])
return np.asarray(
[bezier(a_to_1[: i + 1])(end_prop) for i in range(_len)],
dtype=PointDType,
)
# Shortened version of partial_bezier_points just for quadratics,
# since this is called a fair amount
def partial_quadratic_bezier_points(points, a, b):
points = np.asarray(points, dtype=np.float64)
def partial_quadratic_bezier_points(
points: QuadraticBezierPoints, a: float, b: float
) -> QuadraticBezierPoints:
if a == 1:
return 3 * [points[-1]]
return np.asarray(3 * [points[-1]])
def curve(t):
return (
def curve(t: float) -> Point3D:
return np.asarray(
points[0] * (1 - t) * (1 - t)
+ 2 * points[1] * t * (1 - t)
+ points[2] * t * t
@ -118,10 +142,10 @@ def partial_quadratic_bezier_points(points, a, b):
h1_prime = (1 - a) * points[1] + a * points[2]
end_prop = (b - a) / (1.0 - a)
h1 = (1 - end_prop) * h0 + end_prop * h1_prime
return [h0, h1, h2]
return np.asarray((h0, h1, h2))
def split_quadratic_bezier(points: np.ndarray, t: float) -> np.ndarray:
def split_quadratic_bezier(points: QuadraticBezierPoints, t: float) -> BezierPoints:
"""Split a quadratic Bézier curve at argument ``t`` into two quadratic curves.
Parameters
@ -143,10 +167,10 @@ def split_quadratic_bezier(points: np.ndarray, t: float) -> np.ndarray:
s2 = interpolate(h1, a2, t)
p = interpolate(s1, s2, t)
return np.array([a1, s1, p, p, s2, a2])
return np.array((a1, s1, p, p, s2, a2))
def subdivide_quadratic_bezier(points: Iterable[float], n: int) -> np.ndarray:
def subdivide_quadratic_bezier(points: QuadraticBezierPoints, n: int) -> BezierPoints:
"""Subdivide a quadratic Bézier curve into ``n`` subcurves which have the same shape.
The points at which the curve is split are located at the
@ -178,8 +202,8 @@ def subdivide_quadratic_bezier(points: Iterable[float], n: int) -> np.ndarray:
def quadratic_bezier_remap(
triplets: Iterable[Iterable[float]], new_number_of_curves: int
):
triplets: QuadraticBezierPoints_Array, new_number_of_curves: int
) -> QuadraticBezierPoints_Array:
"""Remaps the number of curves to a higher amount by splitting bezier curves
Parameters
@ -234,7 +258,21 @@ def quadratic_bezier_remap(
# Linear interpolation variants
def interpolate(start: np.ndarray, end: np.ndarray, alpha: float) -> np.ndarray:
@overload
def interpolate(start: float, end: float, alpha: float) -> float:
...
@overload
def interpolate(start: Point3D, end: Point3D, alpha: float) -> Point3D:
...
def interpolate(
start: int | float | Point3D, end: int | float | Point3D, alpha: float | Point3D
) -> float | Point3D:
return (1 - alpha) * start + alpha * end
@ -244,52 +282,192 @@ def integer_interpolate(
alpha: float,
) -> tuple[int, float]:
"""
Alpha is a float between 0 and 1. This returns
an integer between start and end (inclusive) representing
appropriate interpolation between them, along with a
"residue" representing a new proportion between the
returned integer and the next one of the
list.
This is a variant of interpolate that returns an integer and the residual
For example, if start=0, end=10, alpha=0.46, This
would return (4, 0.6).
Parameters
----------
start
The start of the range
end
The end of the range
alpha
a float between 0 and 1.
Returns
-------
tuple[int, float]
This returns an integer between start and end (inclusive) representing
appropriate interpolation between them, along with a
"residue" representing a new proportion between the
returned integer and the next one of the
list.
Example
-------
.. code-block:: pycon
>>> integer, residue = integer_interpolate(start=0, end=10, alpha=0.46)
>>> np.allclose((integer, residue), (4, 0.6))
True
"""
if alpha >= 1:
return (end - 1, 1.0)
return (int(end - 1), 1.0)
if alpha <= 0:
return (start, 0)
return (int(start), 0)
value = int(interpolate(start, end, alpha))
residue = ((end - start) * alpha) % 1
return (value, residue)
@overload
def mid(start: float, end: float) -> float:
...
@overload
def mid(start: Point3D, end: Point3D) -> Point3D:
...
def mid(start: float | Point3D, end: float | Point3D) -> float | Point3D:
"""Returns the midpoint between two values.
Parameters
----------
start
The first value
end
The second value
Returns
-------
The midpoint between the two values
"""
return (start + end) / 2.0
def inverse_interpolate(start: float, end: float, value: float) -> np.ndarray:
@overload
def inverse_interpolate(start: float, end: float, value: float) -> float:
...
@overload
def inverse_interpolate(start: float, end: float, value: Point3D) -> Point3D:
...
@overload
def inverse_interpolate(start: Point3D, end: Point3D, value: Point3D) -> Point3D:
...
def inverse_interpolate(
start: float | Point3D, end: float | Point3D, value: float | Point3D
) -> float | Point3D:
"""Perform inverse interpolation to determine the alpha
values that would produce the specified ``value``
given the ``start`` and ``end`` values or points.
Parameters
----------
start
The start value or point of the interpolation.
end
The end value or point of the interpolation.
value
The value or point for which the alpha value
should be determined.
Returns
-------
The alpha values producing the given input
when interpolating between ``start`` and ``end``.
Example
-------
.. code-block:: pycon
>>> inverse_interpolate(start=2, end=6, value=4)
0.5
>>> start = np.array([1, 2, 1])
>>> end = np.array([7, 8, 11])
>>> value = np.array([4, 5, 5])
>>> inverse_interpolate(start, end, value)
array([0.5, 0.5, 0.4])
"""
return np.true_divide(value - start, end - start)
@overload
def match_interpolate(
new_start: float,
new_end: float,
old_start: float,
old_end: float,
old_value: float,
) -> float:
...
@overload
def match_interpolate(
new_start: float,
new_end: float,
old_start: float,
old_end: float,
old_value: Point3D,
) -> Point3D:
...
def match_interpolate(
new_start: float,
new_end: float,
old_start: float,
old_end: float,
old_value: float,
) -> np.ndarray:
old_value: float | Point3D,
) -> float | Point3D:
"""Interpolate a value from an old range to a new range.
Parameters
----------
new_start
The start of the new range.
new_end
The end of the new range.
old_start
The start of the old range.
old_end
The end of the old range.
old_value
The value within the old range whose corresponding
value in the new range (with the same alpha value)
is desired.
Returns
-------
The interpolated value within the new range.
Examples
--------
>>> match_interpolate(0, 100, 10, 20, 15)
50.0
"""
old_alpha = inverse_interpolate(old_start, old_end, old_value)
return interpolate(
new_start,
new_end,
inverse_interpolate(old_start, old_end, old_value),
old_alpha, # type: ignore
)
# Figuring out which bezier curves most smoothly connect a sequence of points
def get_smooth_cubic_bezier_handle_points(points):
points = np.array(points)
def get_smooth_cubic_bezier_handle_points(
points: Point3D_Array,
) -> tuple[BezierPoints, BezierPoints]:
points = np.asarray(points)
num_handles = len(points) - 1
dim = points.shape[1]
if num_handles < 1:
@ -301,7 +479,7 @@ def get_smooth_cubic_bezier_handle_points(points):
# diag is a representation of the matrix in diagonal form
# See https://www.particleincell.com/2012/bezier-splines/
# for how to arrive at these equations
diag = np.zeros((l + u + 1, 2 * num_handles))
diag: MatrixMN = np.zeros((l + u + 1, 2 * num_handles))
diag[0, 1::2] = -1
diag[0, 2::2] = 1
diag[1, 0::2] = 2
@ -314,13 +492,13 @@ def get_smooth_cubic_bezier_handle_points(points):
# This is the b as in Ax = b, where we are solving for x,
# and A is represented using diag. However, think of entries
# to x and b as being points in space, not numbers
b = np.zeros((2 * num_handles, dim))
b: Point3D_Array = np.zeros((2 * num_handles, dim))
b[1::2] = 2 * points[1:]
b[0] = points[0]
b[-1] = points[-1]
def solve_func(b):
return linalg.solve_banded((l, u), diag, b)
def solve_func(b: ColVector) -> ColVector | MatrixMN:
return linalg.solve_banded((l, u), diag, b) # type: ignore
use_closed_solve_function = is_closed(points)
if use_closed_solve_function:
@ -334,8 +512,8 @@ def get_smooth_cubic_bezier_handle_points(points):
b[0] = 2 * points[0]
b[-1] = np.zeros(dim)
def closed_curve_solve_func(b):
return linalg.solve(matrix, b)
def closed_curve_solve_func(b: ColVector) -> ColVector | MatrixMN:
return linalg.solve(matrix, b) # type: ignore
handle_pairs = np.zeros((2 * num_handles, dim))
for i in range(dim):
@ -347,8 +525,8 @@ def get_smooth_cubic_bezier_handle_points(points):
def get_smooth_handle_points(
points: np.ndarray,
) -> tuple[np.ndarray, np.ndarray]:
points: BezierPoints,
) -> tuple[BezierPoints, BezierPoints]:
"""Given some anchors (points), compute handles so the resulting bezier curve is smooth.
Parameters
@ -362,7 +540,7 @@ def get_smooth_handle_points(
Computed handles.
"""
# NOTE points here are anchors.
points = np.array(points)
points = np.asarray(points)
num_handles = len(points) - 1
dim = points.shape[1]
if num_handles < 1:
@ -374,7 +552,7 @@ def get_smooth_handle_points(
# diag is a representation of the matrix in diagonal form
# See https://www.particleincell.com/2012/bezier-splines/
# for how to arrive at these equations
diag = np.zeros((l + u + 1, 2 * num_handles))
diag: MatrixMN = np.zeros((l + u + 1, 2 * num_handles))
diag[0, 1::2] = -1
diag[0, 2::2] = 1
diag[1, 0::2] = 2
@ -392,8 +570,8 @@ def get_smooth_handle_points(
b[0] = points[0]
b[-1] = points[-1]
def solve_func(b: np.ndarray) -> np.ndarray:
return linalg.solve_banded((l, u), diag, b)
def solve_func(b: ColVector) -> ColVector | MatrixMN:
return linalg.solve_banded((l, u), diag, b) # type: ignore
use_closed_solve_function = is_closed(points)
if use_closed_solve_function:
@ -407,8 +585,8 @@ def get_smooth_handle_points(
b[0] = 2 * points[0]
b[-1] = np.zeros(dim)
def closed_curve_solve_func(b: np.ndarray) -> np.ndarray:
return linalg.solve(matrix, b)
def closed_curve_solve_func(b: ColVector) -> ColVector | MatrixMN:
return linalg.solve(matrix, b) # type: ignore
handle_pairs = np.zeros((2 * num_handles, dim))
for i in range(dim):
@ -419,7 +597,9 @@ def get_smooth_handle_points(
return handle_pairs[0::2], handle_pairs[1::2]
def diag_to_matrix(l_and_u: tuple[int, int], diag: np.ndarray) -> np.ndarray:
def diag_to_matrix(
l_and_u: tuple[int, int], diag: npt.NDArray[Any]
) -> npt.NDArray[Any]:
"""
Converts array whose rows represent diagonal
entries of a matrix into the matrix itself.
@ -438,7 +618,9 @@ def diag_to_matrix(l_and_u: tuple[int, int], diag: np.ndarray) -> np.ndarray:
# Given 4 control points for a cubic bezier curve (or arrays of such)
# return control points for 2 quadratics (or 2n quadratics) approximating them.
def get_quadratic_approximation_of_cubic(a0, h0, h1, a1):
def get_quadratic_approximation_of_cubic(
a0: Point3D, h0: Point3D, h1: Point3D, a1: Point3D
) -> BezierPoints:
a0 = np.array(a0, ndmin=2)
h0 = np.array(h0, ndmin=2)
h1 = np.array(h1, ndmin=2)
@ -486,9 +668,9 @@ def get_quadratic_approximation_of_cubic(a0, h0, h1, a1):
m, n = a0.shape
t_mid = t_mid.repeat(n).reshape((m, n))
# Compute bezier point and tangent at the chosen value of t
mid = bezier([a0, h0, h1, a1])(t_mid)
Tm = bezier([h0 - a0, h1 - h0, a1 - h1])(t_mid)
# Compute bezier point and tangent at the chosen value of t (these are vectorized)
mid = bezier([a0, h0, h1, a1])(t_mid) # type: ignore
Tm = bezier([h0 - a0, h1 - h0, a1 - h1])(t_mid) # type: ignore
# Intersection between tangent lines at end points
# and tangent in the middle
@ -506,15 +688,15 @@ def get_quadratic_approximation_of_cubic(a0, h0, h1, a1):
return result
def is_closed(points: tuple[np.ndarray, np.ndarray]) -> bool:
return np.allclose(points[0], points[-1])
def is_closed(points: Point3D_Array) -> bool:
return np.allclose(points[0], points[-1]) # type: ignore
def proportions_along_bezier_curve_for_point(
point: typing.Iterable[float | int],
control_points: typing.Iterable[typing.Iterable[float | int]],
round_to: float | int | None = 1e-6,
) -> np.ndarray:
point: Point3D,
control_points: BezierPoints,
round_to: float = 1e-6,
) -> npt.NDArray[Any]:
"""Obtains the proportion along the bezier curve corresponding to a given point
given the bezier curve's control points.
@ -583,21 +765,23 @@ def proportions_along_bezier_curve_for_point(
# Roots will be none, but in this specific instance, we don't need to consider that.
continue
bezier_polynom = np.polynomial.Polynomial(terms[::-1])
polynom_roots = bezier_polynom.roots()
polynom_roots = bezier_polynom.roots() # type: ignore
if len(polynom_roots) > 0:
polynom_roots = np.around(polynom_roots, int(np.log10(1 / round_to)))
roots.append(polynom_roots)
roots = [[root for root in rootlist if root.imag == 0] for rootlist in roots]
roots = reduce(np.intersect1d, roots) # Get common roots.
roots = np.array([r.real for r in roots if 0 <= r.real <= 1])
return roots
# Get common roots
# arg-type: ignore
roots = reduce(np.intersect1d, roots) # type: ignore
result = np.asarray([r.real for r in roots if 0 <= r.real <= 1])
return result
def point_lies_on_bezier(
point: typing.Iterable[float | int],
control_points: typing.Iterable[typing.Iterable[float | int]],
round_to: float | int | None = 1e-6,
point: Point3D,
control_points: BezierPoints,
round_to: float = 1e-6,
) -> bool:
"""Checks if a given point lies on the bezier curves with the given control points.

View file

@ -3,43 +3,55 @@ color conversion.
This module contains the implementation of :class:`.ManimColor`,
the data structure internally used to represent colors.
"""
The preferred way of using these colors is by importing their constants from manim:
.. code-block:: pycon
>>> from manim import RED, GREEN, BLUE
>>> print(RED)
#FC6255
Note this way uses the name of the colors in UPPERCASE.
.. note::
The colors of type "C" have an alias equal to the colorname without a letter,
e.g. GREEN = GREEN_C
"""
from __future__ import annotations
# logger = _config.logger
import colorsys
# logger = _config.logger
import random
from typing import Any, Sequence, Union
import re
from typing import Any, Sequence, TypeVar, Union, overload
import numpy as np
from typing_extensions import Literal, TypeAlias
import numpy.typing as npt
from typing_extensions import Self, TypeAlias
from manim.typing import (
HSV_Array_Float,
HSV_Tuple_Float,
ManimColorDType,
ManimColorInternal,
RGB_Array_Float,
RGB_Array_Int,
RGB_Tuple_Float,
RGB_Tuple_Int,
RGBA_Array_Float,
RGBA_Array_Int,
RGBA_Tuple_Float,
RGBA_Tuple_Int,
)
from ...utils.space_ops import normalize
ManimColorDType: TypeAlias = np.float64
ManimFloat: TypeAlias = np.float64
ManimInt: TypeAlias = np.int64
# import manim._config as _config
RGB_Array_Float: TypeAlias = "np.ndarray[Literal[3], np.dtype[ManimFloat]]"
RGB_Tuple_Float: TypeAlias = "tuple[float, float, float]"
RGB_Array_Int: TypeAlias = "np.ndarray[Literal[3], np.dtype[ManimInt]]"
RGB_Tuple_Int: TypeAlias = "tuple[int, int, int]"
RGBA_Array_Float: TypeAlias = "np.ndarray[Literal[4], np.dtype[ManimFloat]]"
RGBA_Tuple_Float: TypeAlias = "tuple[float, float, float, float]"
RGBA_Array_Int: TypeAlias = "np.ndarray[Literal[4], np.dtype[ManimInt]]"
RGBA_Tuple_Int: TypeAlias = "tuple[int, int, int, int]"
HSV_Array_Float: TypeAlias = RGB_Array_Float
HSV_Tuple_Float: TypeAlias = RGB_Tuple_Float
ManimColorInternal: TypeAlias = "np.ndarray[Literal[4], np.dtype[ManimColorDType]]"
import re
re_hex = re.compile("((?<=#)|(?<=0x))[A-F0-9]{6,8}", re.IGNORECASE)
@ -83,7 +95,7 @@ class ManimColor:
def __init__(
self,
value: ParsableManimColor,
value: ParsableManimColor | None,
alpha: float = 1.0,
) -> None:
if value is None:
@ -514,7 +526,7 @@ class ManimColor:
cls,
rgb: RGB_Array_Float | RGB_Tuple_Float | RGB_Array_Int | RGB_Tuple_Int,
alpha: float = 1.0,
) -> ManimColor:
) -> Self:
"""Creates a ManimColor from an RGB Array. Automagically decides which type it is int/float
.. warning::
@ -539,7 +551,7 @@ class ManimColor:
@classmethod
def from_rgba(
cls, rgba: RGBA_Array_Float | RGBA_Tuple_Float | RGBA_Array_Int | RGBA_Tuple_Int
) -> ManimColor:
) -> Self:
"""Creates a ManimColor from an RGBA Array. Automagically decides which type it is int/float
.. warning::
@ -559,7 +571,7 @@ class ManimColor:
return cls(rgba)
@classmethod
def from_hex(cls, hex: str, alpha: float = 1.0) -> ManimColor:
def from_hex(cls, hex: str, alpha: float = 1.0) -> Self:
"""Creates a Manim Color from a hex string, prefixes allowed # and 0x
Parameters
@ -579,7 +591,7 @@ class ManimColor:
@classmethod
def from_hsv(
cls, hsv: HSV_Array_Float | HSV_Tuple_Float, alpha: float = 1.0
) -> ManimColor:
) -> Self:
"""Creates a ManimColor from an HSV Array
Parameters
@ -597,12 +609,30 @@ class ManimColor:
rgb = colorsys.hsv_to_rgb(*hsv)
return cls(rgb, alpha)
@overload
@classmethod
def parse(
cls,
color: ParsableManimColor | None,
alpha: float = ...,
) -> Self:
...
@overload
@classmethod
def parse(
cls,
color: Sequence[ParsableManimColor],
alpha: float = ...,
) -> list[Self]:
...
@classmethod
def parse(
cls,
color: ParsableManimColor | list[ParsableManimColor] | None,
alpha: float = 1.0,
) -> ManimColor | list[ManimColor]:
) -> Self | list[Self]:
"""
Handles the parsing of a list of colors or a single color.
@ -688,6 +718,9 @@ ParsableManimColor: TypeAlias = Union[
"""ParsableManimColor is the representation for all types that are parsable 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`
@ -722,12 +755,12 @@ def color_to_rgba(color: ParsableManimColor, alpha: float = 1) -> RGBA_Array_Flo
return ManimColor(color).to_rgba_with_alpha(alpha)
def color_to_int_rgb(color: ManimColor) -> RGB_Array_Int:
def color_to_int_rgb(color: ParsableManimColor) -> RGB_Array_Int:
"""Helper function for use in functional style programming refer to :meth:`to_int_rgb` in :class:`ManimColor`
Parameters
----------
color : ManimColor
color : ParsableManimColor
A color
Returns
@ -738,12 +771,12 @@ def color_to_int_rgb(color: ManimColor) -> RGB_Array_Int:
return ManimColor(color).to_int_rgb()
def color_to_int_rgba(color: ManimColor, alpha: float = 1.0) -> RGBA_Array_Int:
def color_to_int_rgba(color: ParsableManimColor, alpha: float = 1.0) -> RGBA_Array_Int:
"""Helper function for use in functional style programming refer to :meth:`to_int_rgba_with_alpha` in :class:`ManimColor`
Parameters
----------
color : ManimColor
color : ParsableManimColor
A color
alpha : float, optional
alpha value to be used in the color, by default 1.0
@ -756,7 +789,9 @@ def color_to_int_rgba(color: ManimColor, alpha: float = 1.0) -> RGBA_Array_Int:
return ManimColor(color).to_int_rgba_with_alpha(alpha)
def rgb_to_color(rgb: RGB_Array_Float | RGB_Tuple_Float) -> ManimColor:
def rgb_to_color(
rgb: RGB_Array_Float | RGB_Tuple_Float | RGB_Array_Int | RGB_Tuple_Int,
) -> ManimColor:
"""Helper function for use in functional style programming refer to :meth:`from_rgb` in :class:`ManimColor`
Parameters
@ -772,7 +807,9 @@ def rgb_to_color(rgb: RGB_Array_Float | RGB_Tuple_Float) -> ManimColor:
return ManimColor.from_rgb(rgb)
def rgba_to_color(rgba: RGBA_Array_Float | RGBA_Tuple_Float) -> ManimColor:
def rgba_to_color(
rgba: RGBA_Array_Float | RGBA_Tuple_Float | RGBA_Array_Int | RGBA_Tuple_Int,
) -> ManimColor:
"""Helper function for use in functional style programming refer to :meth:`from_rgba` in :class:`ManimColor`
Parameters
@ -788,7 +825,9 @@ def rgba_to_color(rgba: RGBA_Array_Float | RGBA_Tuple_Float) -> ManimColor:
return ManimColor.from_rgba(rgba)
def rgb_to_hex(rgb: RGB_Array_Float | RGB_Tuple_Float) -> str:
def rgb_to_hex(
rgb: RGB_Array_Float | RGB_Tuple_Float | RGB_Array_Int | RGB_Tuple_Int,
) -> str:
"""Helper function for use in functional style programming refer to :meth:`from_rgb` in :class:`ManimColor`
Parameters
@ -820,7 +859,7 @@ def hex_to_rgb(hex_code: str) -> RGB_Array_Float:
return ManimColor(hex_code).to_rgb()
def invert_color(color: ManimColor) -> ManimColor:
def invert_color(color: ManimColorT) -> ManimColorT:
"""Helper function for use in functional style programming refer to :meth:`invert` in :class:`ManimColor`
Parameters
@ -837,15 +876,15 @@ def invert_color(color: ManimColor) -> ManimColor:
def interpolate_arrays(
arr1: np.ndarray[Any, Any], arr2: np.ndarray[Any, Any], alpha: float
arr1: npt.NDArray[Any], arr2: npt.NDArray[Any], alpha: float
) -> np.ndarray:
"""Helper function used in Manim to fade between two objects smoothly
Parameters
----------
arr1 : np.ndarray[Any, Any]
arr1 : npt.NDArray[Any]
The first array of colors
arr2 : np.ndarray[Any, Any]
arr2 : npt.NDArray[Any]
The second array of colors
alpha : float
The alpha value corresponding to the interpolation point between the two inputs
@ -880,7 +919,7 @@ def color_gradient(
return ManimColor(reference_colors[0])
if len(reference_colors) == 1:
return [ManimColor(reference_colors[0])] * length_of_output
rgbs = list(map(color_to_rgb, reference_colors))
rgbs = [color_to_rgb(color) for color in reference_colors]
alphas = np.linspace(0, (len(rgbs) - 1), length_of_output)
floors = alphas.astype("int")
alphas_mod1 = alphas % 1
@ -894,8 +933,8 @@ def color_gradient(
def interpolate_color(
color1: ManimColor, color2: ManimColor, alpha: float
) -> ManimColor:
color1: ManimColorT, color2: ManimColor, alpha: float
) -> ManimColorT:
"""Standalone function to interpolate two ManimColors and get the result refer to :meth:`interpolate` in :class:`ManimColor`
Parameters
@ -915,7 +954,7 @@ def interpolate_color(
return color1.interpolate(color2, alpha)
def average_color(*colors: ManimColor) -> ManimColor:
def average_color(*colors: ParsableManimColor) -> ManimColor:
"""Determines the Average color of the given parameters
Returns
@ -923,7 +962,7 @@ def average_color(*colors: ManimColor) -> ManimColor:
ManimColor
The average color of the input
"""
rgbs = np.array(list(map(color_to_rgb, colors)))
rgbs = np.array([color_to_rgb(color) for color in colors])
mean_rgb = np.apply_along_axis(np.mean, 0, rgbs)
return rgb_to_color(mean_rgb)
@ -939,8 +978,7 @@ def random_bright_color() -> ManimColor:
ManimColor
A bright ManimColor
"""
color = random_color()
curr_rgb = color_to_rgb(color)
curr_rgb = color_to_rgb(random_color())
new_rgb = interpolate_arrays(curr_rgb, np.ones(len(curr_rgb)), 0.5)
return ManimColor(new_rgb)
@ -962,10 +1000,10 @@ def random_color() -> ManimColor:
def get_shaded_rgb(
rgb: np.ndarray,
point: np.ndarray,
unit_normal_vect: np.ndarray,
light_source: np.ndarray,
rgb: npt.NDArray[Any],
point: npt.NDArray[Any],
unit_normal_vect: npt.NDArray[Any],
light_source: npt.NDArray[Any],
) -> RGBA_Array_Float:
to_sun = normalize(light_source - point)
factor = 0.5 * np.dot(unit_normal_vect, to_sun) ** 3

View file

@ -125,87 +125,87 @@ from typing import List
from .core import ManimColor
WHITE: ManimColor = ManimColor("#FFFFFF")
GRAY_A: ManimColor = ManimColor("#DDDDDD")
GREY_A: ManimColor = ManimColor("#DDDDDD")
GRAY_B: ManimColor = ManimColor("#BBBBBB")
GREY_B: ManimColor = ManimColor("#BBBBBB")
GRAY_C: ManimColor = ManimColor("#888888")
GREY_C: ManimColor = ManimColor("#888888")
GRAY_D: ManimColor = ManimColor("#444444")
GREY_D: ManimColor = ManimColor("#444444")
GRAY_E: ManimColor = ManimColor("#222222")
GREY_E: ManimColor = ManimColor("#222222")
BLACK: ManimColor = ManimColor("#000000")
LIGHTER_GRAY: ManimColor = ManimColor("#DDDDDD")
LIGHTER_GREY: ManimColor = ManimColor("#DDDDDD")
LIGHT_GRAY: ManimColor = ManimColor("#BBBBBB")
LIGHT_GREY: ManimColor = ManimColor("#BBBBBB")
GRAY: ManimColor = ManimColor("#888888")
GREY: ManimColor = ManimColor("#888888")
DARK_GRAY: ManimColor = ManimColor("#444444")
DARK_GREY: ManimColor = ManimColor("#444444")
DARKER_GRAY: ManimColor = ManimColor("#222222")
DARKER_GREY: ManimColor = ManimColor("#222222")
BLUE_A: ManimColor = ManimColor("#C7E9F1")
BLUE_B: ManimColor = ManimColor("#9CDCEB")
BLUE_C: ManimColor = ManimColor("#58C4DD")
BLUE_D: ManimColor = ManimColor("#29ABCA")
BLUE_E: ManimColor = ManimColor("#236B8E")
PURE_BLUE: ManimColor = ManimColor("#0000FF")
BLUE: ManimColor = ManimColor("#58C4DD")
DARK_BLUE: ManimColor = ManimColor("#236B8E")
TEAL_A: ManimColor = ManimColor("#ACEAD7")
TEAL_B: ManimColor = ManimColor("#76DDC0")
TEAL_C: ManimColor = ManimColor("#5CD0B3")
TEAL_D: ManimColor = ManimColor("#55C1A7")
TEAL_E: ManimColor = ManimColor("#49A88F")
TEAL: ManimColor = ManimColor("#5CD0B3")
GREEN_A: ManimColor = ManimColor("#C9E2AE")
GREEN_B: ManimColor = ManimColor("#A6CF8C")
GREEN_C: ManimColor = ManimColor("#83C167")
GREEN_D: ManimColor = ManimColor("#77B05D")
GREEN_E: ManimColor = ManimColor("#699C52")
PURE_GREEN: ManimColor = ManimColor("#00FF00")
GREEN: ManimColor = ManimColor("#83C167")
YELLOW_A: ManimColor = ManimColor("#FFF1B6")
YELLOW_B: ManimColor = ManimColor("#FFEA94")
YELLOW_C: ManimColor = ManimColor("#FFFF00")
YELLOW_D: ManimColor = ManimColor("#F4D345")
YELLOW_E: ManimColor = ManimColor("#E8C11C")
YELLOW: ManimColor = ManimColor("#FFFF00")
GOLD_A: ManimColor = ManimColor("#F7C797")
GOLD_B: ManimColor = ManimColor("#F9B775")
GOLD_C: ManimColor = ManimColor("#F0AC5F")
GOLD_D: ManimColor = ManimColor("#E1A158")
GOLD_E: ManimColor = ManimColor("#C78D46")
GOLD: ManimColor = ManimColor("#F0AC5F")
RED_A: ManimColor = ManimColor("#F7A1A3")
RED_B: ManimColor = ManimColor("#FF8080")
RED_C: ManimColor = ManimColor("#FC6255")
RED_D: ManimColor = ManimColor("#E65A4C")
RED_E: ManimColor = ManimColor("#CF5044")
PURE_RED: ManimColor = ManimColor("#FF0000")
RED: ManimColor = ManimColor("#FC6255")
MAROON_A: ManimColor = ManimColor("#ECABC1")
MAROON_B: ManimColor = ManimColor("#EC92AB")
MAROON_C: ManimColor = ManimColor("#C55F73")
MAROON_D: ManimColor = ManimColor("#A24D61")
MAROON_E: ManimColor = ManimColor("#94424F")
MAROON: ManimColor = ManimColor("#C55F73")
PURPLE_A: ManimColor = ManimColor("#CAA3E8")
PURPLE_B: ManimColor = ManimColor("#B189C6")
PURPLE_C: ManimColor = ManimColor("#9A72AC")
PURPLE_D: ManimColor = ManimColor("#715582")
PURPLE_E: ManimColor = ManimColor("#644172")
PURPLE: ManimColor = ManimColor("#9A72AC")
PINK: ManimColor = ManimColor("#D147BD")
LIGHT_PINK: ManimColor = ManimColor("#DC75CD")
ORANGE: ManimColor = ManimColor("#FF862F")
LIGHT_BROWN: ManimColor = ManimColor("#CD853F")
DARK_BROWN: ManimColor = ManimColor("#8B4513")
GRAY_BROWN: ManimColor = ManimColor("#736357")
GREY_BROWN: ManimColor = ManimColor("#736357")
WHITE = ManimColor("#FFFFFF")
GRAY_A = ManimColor("#DDDDDD")
GREY_A = ManimColor("#DDDDDD")
GRAY_B = ManimColor("#BBBBBB")
GREY_B = ManimColor("#BBBBBB")
GRAY_C = ManimColor("#888888")
GREY_C = ManimColor("#888888")
GRAY_D = ManimColor("#444444")
GREY_D = ManimColor("#444444")
GRAY_E = ManimColor("#222222")
GREY_E = ManimColor("#222222")
BLACK = ManimColor("#000000")
LIGHTER_GRAY = ManimColor("#DDDDDD")
LIGHTER_GREY = ManimColor("#DDDDDD")
LIGHT_GRAY = ManimColor("#BBBBBB")
LIGHT_GREY = ManimColor("#BBBBBB")
GRAY = ManimColor("#888888")
GREY = ManimColor("#888888")
DARK_GRAY = ManimColor("#444444")
DARK_GREY = ManimColor("#444444")
DARKER_GRAY = ManimColor("#222222")
DARKER_GREY = ManimColor("#222222")
BLUE_A = ManimColor("#C7E9F1")
BLUE_B = ManimColor("#9CDCEB")
BLUE_C = ManimColor("#58C4DD")
BLUE_D = ManimColor("#29ABCA")
BLUE_E = ManimColor("#236B8E")
PURE_BLUE = ManimColor("#0000FF")
BLUE = ManimColor("#58C4DD")
DARK_BLUE = ManimColor("#236B8E")
TEAL_A = ManimColor("#ACEAD7")
TEAL_B = ManimColor("#76DDC0")
TEAL_C = ManimColor("#5CD0B3")
TEAL_D = ManimColor("#55C1A7")
TEAL_E = ManimColor("#49A88F")
TEAL = ManimColor("#5CD0B3")
GREEN_A = ManimColor("#C9E2AE")
GREEN_B = ManimColor("#A6CF8C")
GREEN_C = ManimColor("#83C167")
GREEN_D = ManimColor("#77B05D")
GREEN_E = ManimColor("#699C52")
PURE_GREEN = ManimColor("#00FF00")
GREEN = ManimColor("#83C167")
YELLOW_A = ManimColor("#FFF1B6")
YELLOW_B = ManimColor("#FFEA94")
YELLOW_C = ManimColor("#FFFF00")
YELLOW_D = ManimColor("#F4D345")
YELLOW_E = ManimColor("#E8C11C")
YELLOW = ManimColor("#FFFF00")
GOLD_A = ManimColor("#F7C797")
GOLD_B = ManimColor("#F9B775")
GOLD_C = ManimColor("#F0AC5F")
GOLD_D = ManimColor("#E1A158")
GOLD_E = ManimColor("#C78D46")
GOLD = ManimColor("#F0AC5F")
RED_A = ManimColor("#F7A1A3")
RED_B = ManimColor("#FF8080")
RED_C = ManimColor("#FC6255")
RED_D = ManimColor("#E65A4C")
RED_E = ManimColor("#CF5044")
PURE_RED = ManimColor("#FF0000")
RED = ManimColor("#FC6255")
MAROON_A = ManimColor("#ECABC1")
MAROON_B = ManimColor("#EC92AB")
MAROON_C = ManimColor("#C55F73")
MAROON_D = ManimColor("#A24D61")
MAROON_E = ManimColor("#94424F")
MAROON = ManimColor("#C55F73")
PURPLE_A = ManimColor("#CAA3E8")
PURPLE_B = ManimColor("#B189C6")
PURPLE_C = ManimColor("#9A72AC")
PURPLE_D = ManimColor("#715582")
PURPLE_E = ManimColor("#644172")
PURPLE = ManimColor("#9A72AC")
PINK = ManimColor("#D147BD")
LIGHT_PINK = ManimColor("#DC75CD")
ORANGE = ManimColor("#FF862F")
LIGHT_BROWN = ManimColor("#CD853F")
DARK_BROWN = ManimColor("#8B4513")
GRAY_BROWN = ManimColor("#736357")
GREY_BROWN = ManimColor("#736357")
# Colors used for Manim Community's logo and banner

View file

@ -2,6 +2,8 @@
from __future__ import annotations
from manim.typing import Point3D_Array, Vector
__all__ = [
"quaternion_mult",
"quaternion_from_angle_axis",
@ -37,7 +39,6 @@ __all__ = [
import itertools as it
import math
from typing import Sequence
import numpy as np
@ -108,7 +109,7 @@ def quaternion_from_angle_axis(
"""
if not axis_normalized:
axis = normalize(axis)
return [math.cos(angle / 2), *(math.sin(angle / 2) * axis)]
return [np.cos(angle / 2), *(np.sin(angle / 2) * axis)]
def angle_axis_from_quaternion(quaternion: Sequence[float]) -> Sequence[float]:
@ -256,7 +257,7 @@ def rotation_about_z(angle: float) -> np.ndarray:
np.ndarray
Gives back the rotated matrix.
"""
c, s = math.cos(angle), math.sin(angle)
c, s = np.cos(angle), np.sin(angle)
return np.array(
[
[c, -s, 0],
@ -540,10 +541,10 @@ def line_intersection(
def find_intersection(
p0s: Sequence[np.ndarray],
v0s: Sequence[np.ndarray],
p1s: Sequence[np.ndarray],
v1s: Sequence[np.ndarray],
p0s: Sequence[np.ndarray] | Point3D_Array,
v0s: Sequence[np.ndarray] | Point3D_Array,
p1s: Sequence[np.ndarray] | Point3D_Array,
v1s: Sequence[np.ndarray] | Point3D_Array,
threshold: float = 1e-5,
) -> Sequence[np.ndarray]:
"""
@ -621,7 +622,38 @@ def shoelace_direction(x_y: np.ndarray) -> str:
return "CW" if area > 0 else "CCW"
def cross2d(a, b):
def cross2d(
a: Sequence[Vector] | Vector, b: Sequence[Vector] | Vector
) -> Sequence[float] | float:
"""Compute the determinant(s) of the passed
vector (sequences).
Parameters
----------
a
A vector or a sequence of vectors.
b
A vector or a sequence of vectors.
Returns
-------
Sequence[float] | float
The determinant or sequence of determinants
of the first two components of the specified
vectors.
Examples
--------
.. code-block:: pycon
>>> cross2d(np.array([1, 2]), np.array([3, 4]))
-2
>>> cross2d(
... np.array([[1, 2, 0], [1, 0, 0]]),
... np.array([[3, 4, 0], [0, 1, 0]]),
... )
array([-2, 1])
"""
if len(a.shape) == 2:
return a[:, 0] * b[:, 1] - a[:, 1] * b[:, 0]
else:

View file

@ -1,34 +1,90 @@
[mypy]
show_error_codes = True
strict = False
files = manim
python_version = 3.10
; plugins = numpy.typing.mypy_plugin
ignore_errors = False
cache_fine_grained = True
warn_unused_ignores = True
# ignore most files; should be checked once proper types have been implemented
[mypy-manim.__main__]
# Disallow Dynamic Typing
# disallow_any_unimported = True
# disallow_any_expr = False
# disallow_any_decorated = True
# disallow_any_explicit = True
# disallow_any_generics = True
# disallow_subclassing_any = True
#
# # Disallow Untyped Defs and Calls
disallow_untyped_calls = True
disallow_untyped_defs = True
disallow_incomplete_defs = True
# check_untyped_defs = False
# disallow_untyped_decorators = True
#
# # None and Optional Handling
# implicit_optional = False
# strict_optional = True
#
# # Configuring Warnings
# warn_redundant_casts = True
# warn_unused_ignores = True
warn_return_any = True
# warn_unreachable = True
#
# # Strictness Flags
# allow_untyped_globals = False
# allow_redefinition = False
# local_partial_types = False
# strict_equality = True
#
# # Configuring Error Messages
# show_error_context = True
# show_column_numbers = True
# show_error_codes = True
# pretty = True
# color_output = True
# error_summary = True
#
# disable_recursive_aliases = True
[mypy-manim._config.*]
ignore_errors = True
[mypy-manim.animation.*]
ignore_errors = True
[mypy-manim.camera.*]
ignore_errors = True
[mypy-manim.scene.*]
[mypy-manim.cli.*]
ignore_errors = True
[mypy-manim.cli.cfg.*]
ignore_errors = True
[mypy-manim.gui.*]
ignore_errors = True
[mypy-manim.mobject.*]
ignore_errors = True
[mypy-manim._config.*]
[mypy-manim.plugins.*]
ignore_errors = True
[mypy-manim.renderer.*]
ignore_errors = True
[mypy-manim.scene.*]
ignore_errors = True
[mypy-manim.utils.*]
ignore_errors = True
[mypy-manim.utils.color]
ignore_errors = False
[mypy-manim.animation.*]
[mypy-manim.__main__]
ignore_errors = True
# ---------------- We can't properly type this ------------------------
[mypy-manim.grpc.*]
@ -40,10 +96,6 @@ ignore_errors = True
[mypy-manimpango]
ignore_missing_imports = True
# Has stubs in 3.8
[mypy-numpy]
ignore_missing_imports = True
# Has stubs in 3.8
[mypy-pydub]
ignore_missing_imports = True
@ -66,9 +118,6 @@ ignore_missing_imports = True
[mypy-moderngl_window.*]
ignore_missing_imports = True
[mypy-colour]
ignore_missing_imports = True
[mypy-dearpygui.*]
ignore_missing_imports = True

501
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -29,7 +29,7 @@ packages = [
python = ">=3.8,<3.12"
click = ">=7.2,<=9.0"
click-default-group = "^1.2.2"
numpy = "^1.19"
numpy = ">=1.22"
Pillow = ">=9.1,<10.0"
scipy = "^1.7.3"
tqdm = "^4.62.3"
@ -78,12 +78,11 @@ pygithub = "^1"
flake8 = "^3.9.0"
isort = "^5.8.0"
pytest-xdist = "^2.2"
mypy = "^0.931"
types-requests = "^2.25.6"
types-protobuf = "^3.17.4"
types-decorator = "^0.1.7"
types-setuptools = "^57.0.2"
types-Pillow = "^8.3.3"
types-Pillow = "^9.3.0.4"
types-Pygments = "^2.9.2"
flake8-builtins = "^1.5.3"
flake8-bugbear = "^21.4.3"