Merge branch 'main' into FixForIssue4617

This commit is contained in:
Henrik Skov Midtiby 2026-03-12 11:05:26 +01:00 committed by GitHub
commit 6b23399a7e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 316 additions and 204 deletions

View file

@ -10,7 +10,7 @@ from __future__ import annotations
__all__ = ["MovingCamera"]
from collections.abc import Iterable
from typing import Any
from typing import Any, Literal, overload
from cairo import Context
@ -20,7 +20,7 @@ from .. import config
from ..camera.camera import Camera
from ..constants import DOWN, LEFT, RIGHT, UP
from ..mobject.frame import ScreenRectangle
from ..mobject.mobject import Mobject
from ..mobject.mobject import Mobject, _AnimationBuilder
from ..utils.color import WHITE, ManimColor
@ -166,13 +166,31 @@ class MovingCamera(Camera):
"""
return [self.frame]
@overload
def auto_zoom(
self,
mobjects: Iterable[Mobject],
margin: float,
only_mobjects_in_frame: bool,
animate: Literal[False],
) -> Mobject: ...
@overload
def auto_zoom(
self,
mobjects: Iterable[Mobject],
margin: float,
only_mobjects_in_frame: bool,
animate: Literal[True],
) -> _AnimationBuilder: ...
def auto_zoom(
self,
mobjects: Iterable[Mobject],
margin: float = 0,
only_mobjects_in_frame: bool = False,
animate: bool = True,
) -> Mobject:
) -> _AnimationBuilder | Mobject:
"""Zooms on to a given array of mobjects (or a singular mobject)
and automatically resizes to frame all the mobjects.

View file

@ -14,14 +14,14 @@ __all__ = [
"RightAngle",
]
from typing import TYPE_CHECKING, Any, Literal
from typing import TYPE_CHECKING, Any, Literal, cast
import numpy as np
from manim import config
from manim.constants import *
from manim.mobject.geometry.arc import Arc, ArcBetweenPoints, Dot, TipableVMobject
from manim.mobject.geometry.tips import ArrowTriangleFilledTip
from manim.mobject.geometry.tips import ArrowTip, ArrowTriangleFilledTip
from manim.mobject.mobject import Mobject
from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL
from manim.mobject.opengl.opengl_mobject import OpenGLMobject
@ -648,9 +648,11 @@ class Arrow(Line):
self._set_stroke_width_from_length()
if has_tip:
self.add_tip(tip=old_tips[0])
# error: Argument "tip" to "add_tip" of "TipableVMobject" has incompatible type "VMobject"; expected "ArrowTip | None" [arg-type]
self.add_tip(tip=cast(ArrowTip, old_tips[0]))
if has_start_tip:
self.add_tip(tip=old_tips[1], at_start=True)
# error: Argument "tip" to "add_tip" of "TipableVMobject" has incompatible type "VMobject"; expected "ArrowTip | None" [arg-type]
self.add_tip(tip=cast(ArrowTip, old_tips[1]), at_start=True)
return self
def get_normal_vector(self) -> Vector3D:

View file

@ -207,13 +207,11 @@ class SampleSpace(Rectangle):
if hasattr(parts, subattr):
self.add(getattr(parts, subattr))
def __getitem__(self, index: int) -> SampleSpace:
def __getitem__(self, index: int) -> VMobject:
if hasattr(self, "horizontal_parts"):
val: SampleSpace = self.horizontal_parts[index]
return val
return self.horizontal_parts[index]
elif hasattr(self, "vertical_parts"):
val = self.vertical_parts[index]
return val
return self.vertical_parts[index]
return self.split()[index]

View file

@ -40,15 +40,15 @@ __all__ = [
import itertools as it
from collections.abc import Callable, Iterable, Sequence
from collections.abc import Callable, Iterable
from typing import Any, Self
import numpy as np
from manim.mobject.mobject import Mobject
from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL
from manim.mobject.text.numbers import DecimalNumber, Integer
from manim.mobject.text.tex_mobject import MathTex, Tex
from manim.typing import Vector2DLike, Vector3DLike
from ..constants import *
from ..mobject.types.vectorized_mobject import VGroup, VMobject
@ -164,16 +164,16 @@ class Matrix(VMobject, metaclass=ConvertToOpenGL):
def __init__(
self,
matrix: Iterable,
matrix: Iterable[Iterable[Any] | Vector2DLike],
v_buff: float = 0.8,
h_buff: float = 1.3,
bracket_h_buff: float = MED_SMALL_BUFF,
bracket_v_buff: float = MED_SMALL_BUFF,
add_background_rectangles_to_entries: bool = False,
include_background_rectangle: bool = False,
element_to_mobject: type[Mobject] | Callable[..., Mobject] = MathTex,
element_to_mobject_config: dict = {},
element_alignment_corner: Sequence[float] = DR,
element_to_mobject: type[VMobject] | Callable[..., VMobject] = MathTex,
element_to_mobject_config: dict[str, Any] = {},
element_alignment_corner: Vector3DLike = DR,
left_bracket: str = "[",
right_bracket: str = "]",
stretch_brackets: bool = True,
@ -206,7 +206,9 @@ class Matrix(VMobject, metaclass=ConvertToOpenGL):
if self.include_background_rectangle:
self.add_background_rectangle()
def _matrix_to_mob_matrix(self, matrix: np.ndarray) -> list[list[Mobject]]:
def _matrix_to_mob_matrix(
self, matrix: Iterable[Iterable[Any]]
) -> list[list[VMobject]]:
return [
[
self.element_to_mobject(item, **self.element_to_mobject_config)
@ -215,7 +217,7 @@ class Matrix(VMobject, metaclass=ConvertToOpenGL):
for row in matrix
]
def _organize_mob_matrix(self, matrix: list[list[Mobject]]) -> Self:
def _organize_mob_matrix(self, matrix: list[list[VMobject]]) -> Self:
for i, row in enumerate(matrix):
for j, _ in enumerate(row):
mob = matrix[i][j]
@ -401,7 +403,7 @@ class Matrix(VMobject, metaclass=ConvertToOpenGL):
mob.add_background_rectangle()
return self
def get_mob_matrix(self) -> list[list[Mobject]]:
def get_mob_matrix(self) -> list[list[VMobject]]:
"""Return the underlying mob matrix mobjects.
Returns
@ -483,8 +485,8 @@ class DecimalMatrix(Matrix):
def __init__(
self,
matrix: Iterable,
element_to_mobject: type[Mobject] = DecimalNumber,
matrix: Iterable[Iterable[Any]],
element_to_mobject: type[VMobject] | Callable[..., VMobject] = DecimalNumber,
element_to_mobject_config: dict[str, Any] = {"num_decimal_places": 1},
**kwargs: Any,
):
@ -528,8 +530,8 @@ class IntegerMatrix(Matrix):
def __init__(
self,
matrix: Iterable,
element_to_mobject: type[Mobject] = Integer,
matrix: Iterable[Iterable[Any]],
element_to_mobject: type[VMobject] | Callable[..., VMobject] = Integer,
**kwargs: Any,
):
"""
@ -566,8 +568,8 @@ class MobjectMatrix(Matrix):
def __init__(
self,
matrix: Iterable,
element_to_mobject: type[Mobject] | Callable[..., Mobject] = lambda m: m,
matrix: Iterable[Iterable[Any]],
element_to_mobject: type[VMobject] | Callable[..., VMobject] = lambda m: m,
**kwargs: Any,
):
super().__init__(matrix, element_to_mobject=element_to_mobject, **kwargs)

View file

@ -14,10 +14,10 @@ import random
import sys
import types
import warnings
from collections.abc import Callable, Iterable
from collections.abc import Callable, Iterable, Iterator, Sequence
from functools import partialmethod, reduce
from pathlib import Path
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, cast
import numpy as np
@ -43,24 +43,30 @@ from ..utils.space_ops import angle_between_vectors, normalize, rotation_matrix
if TYPE_CHECKING:
from typing import Self, TypeAlias
from PIL import Image
from manim.mobject.types.point_cloud_mobject import Point
from manim.typing import (
FunctionOverride,
MappingFunction,
MatrixMN,
MultiMappingFunction,
PathFuncType,
PixelArray,
Point3D,
Point3D_Array,
Point3DLike,
Point3DLike_Array,
Vector3D,
Vector3DLike,
)
from ..animation.animation import Animation
from ..camera.camera import Camera
TimeBasedUpdater: TypeAlias = Callable[["Mobject", float], object]
NonTimeBasedUpdater: TypeAlias = Callable[["Mobject"], object]
Updater: TypeAlias = NonTimeBasedUpdater | TimeBasedUpdater
_TimeBasedUpdater: TypeAlias = Callable[["Mobject", float], object]
_NonTimeBasedUpdater: TypeAlias = Callable[["Mobject"], object]
_Updater: TypeAlias = _NonTimeBasedUpdater | _TimeBasedUpdater
class Mobject:
@ -83,16 +89,18 @@ class Mobject:
"""
animation_overrides = {}
original_id: str
_original__init__: Callable[..., None]
animation_overrides: dict[
type[Animation],
FunctionOverride,
] = {}
@classmethod
def __init_subclass__(cls, **kwargs) -> None:
def __init_subclass__(cls, **kwargs: Any) -> None:
super().__init_subclass__(**kwargs)
cls.animation_overrides: dict[
type[Animation],
FunctionOverride,
] = {}
cls.animation_overrides = {}
cls._add_intrinsic_animation_overrides()
cls._original__init__ = cls.__init__
@ -101,16 +109,16 @@ class Mobject:
color: ParsableManimColor | list[ParsableManimColor] = WHITE,
name: str | None = None,
dim: int = 3,
target=None,
target: Mobject | None = None,
z_index: float = 0,
) -> None:
):
self.name = self.__class__.__name__ if name is None else name
self.dim = dim
self.target = target
self.z_index = z_index
self.point_hash = None
self.submobjects = []
self.updaters: list[Updater] = []
self.submobjects: list[Mobject] = []
self.updaters: list[_Updater] = []
self.updating_suspended = False
self.color = ManimColor.parse(color)
@ -151,7 +159,7 @@ class Mobject:
return self._assert_valid_submobjects_internal(submobjects, Mobject)
def _assert_valid_submobjects_internal(
self, submobjects: list[Mobject], mob_class: type[Mobject]
self, submobjects: Iterable[Mobject], mob_class: type[Mobject]
) -> Self:
for i, submob in enumerate(submobjects):
if not isinstance(submob, mob_class):
@ -247,7 +255,7 @@ class Mobject:
)
@classmethod
def set_default(cls, **kwargs) -> None:
def set_default(cls, **kwargs: Any) -> None:
"""Sets the default values of keyword arguments.
If this method is called without any additional keyword
@ -290,8 +298,11 @@ class Mobject:
"""
if kwargs:
cls.__init__ = partialmethod(cls.__init__, **kwargs)
# Apparently mypy does not correctly understand `partialmethod`:
# see https://github.com/python/mypy/issues/8619
cls.__init__ = partialmethod(cls.__init__, **kwargs) # type: ignore[assignment]
else:
# error: Cannot assign to a method [method-assign]
cls.__init__ = cls._original__init__
@property
@ -430,7 +441,7 @@ class Mobject:
# can't use typing.cast because Self is under TYPE_CHECKING
return _UpdaterBuilder(self) # type: ignore[return-value]
def __deepcopy__(self, clone_from_id) -> Self:
def __deepcopy__(self, clone_from_id: dict[int, Mobject]) -> Self:
cls = self.__class__
result = cls.__new__(cls)
clone_from_id[id(self)] = result
@ -447,7 +458,7 @@ class Mobject:
self.points = np.zeros((0, self.dim))
return self
def init_colors(self) -> object:
def init_colors(self, propagate_colors: bool = True) -> object:
"""Initializes the colors.
Gets called upon creation. This is an empty method that can be implemented by
@ -567,10 +578,10 @@ class Mobject:
self._assert_valid_submobjects([mobject])
self.submobjects.insert(index, mobject)
def __add__(self, mobject: Mobject):
def __add__(self, mobject: Mobject) -> Self:
raise NotImplementedError
def __iadd__(self, mobject: Mobject):
def __iadd__(self, mobject: Mobject) -> Self:
raise NotImplementedError
def add_to_back(self, *mobjects: Mobject) -> Self:
@ -650,13 +661,13 @@ class Mobject:
self.submobjects.remove(mobject)
return self
def __sub__(self, other):
def __sub__(self, other: Mobject) -> Self:
raise NotImplementedError
def __isub__(self, other):
def __isub__(self, other: Mobject) -> Self:
raise NotImplementedError
def set(self, **kwargs) -> Self:
def set(self, **kwargs: Any) -> Self:
"""Sets attributes.
I.e. ``my_mobject.set(foo=1)`` applies ``my_mobject.foo = 1``.
@ -724,7 +735,7 @@ class Mobject:
# Remove the "get_" prefix
to_get = attr[4:]
def getter(self):
def getter(self: Mobject) -> Any:
warnings.warn(
"This method is not guaranteed to stay around. Please prefer "
"getting the attribute normally.",
@ -741,7 +752,7 @@ class Mobject:
# Remove the "set_" prefix
to_set = attr[4:]
def setter(self, value):
def setter(self: Mobject, value: Any) -> Mobject:
warnings.warn(
"This method is not guaranteed to stay around. Please prefer "
"setting the attribute normally or with Mobject.set().",
@ -792,7 +803,7 @@ class Mobject:
return self.length_over_dim(0)
@width.setter
def width(self, value: float):
def width(self, value: float) -> None:
self.scale_to_fit_width(value)
@property
@ -828,7 +839,7 @@ class Mobject:
return self.length_over_dim(1)
@height.setter
def height(self, value: float):
def height(self, value: float) -> None:
self.scale_to_fit_height(value)
@property
@ -848,7 +859,7 @@ class Mobject:
return self.length_over_dim(2)
@depth.setter
def depth(self, value: float):
def depth(self, value: float) -> None:
self.scale_to_fit_depth(value)
# Can't be staticmethod because of point_cloud_mobject.py
@ -861,16 +872,13 @@ class Mobject:
return self
# Displaying
def get_image(self, camera=None) -> PixelArray:
def get_image(self, camera: Camera | None = None) -> Image.Image:
if camera is None:
from ..camera.camera import Camera
camera = Camera()
camera.capture_mobject(self)
return camera.get_image()
def show(self, camera=None) -> None:
def show(self, camera: Camera | None = None) -> None:
self.get_image(camera=camera).show()
def save_image(self, name: str | None = None) -> None:
@ -930,18 +938,21 @@ class Mobject:
:meth:`get_updaters`
"""
if not self.updating_suspended:
for updater in self.updaters:
if "dt" in inspect.signature(updater).parameters:
updater(self, dt)
else:
updater(self)
if self.updating_suspended:
return self
for updater in self.updaters:
if "dt" in inspect.signature(updater).parameters:
time_based_updater = cast(_TimeBasedUpdater, updater)
time_based_updater(self, dt)
else:
non_time_based_updater = cast(_NonTimeBasedUpdater, updater)
non_time_based_updater(self)
if recursive:
for submob in self.submobjects:
submob.update(dt, recursive=recursive)
return self
def get_time_based_updaters(self) -> list[TimeBasedUpdater]:
def get_time_based_updaters(self) -> list[_TimeBasedUpdater]:
"""Return all updaters using the ``dt`` parameter.
The updaters use this parameter as the input for difference in time.
@ -957,11 +968,12 @@ class Mobject:
:meth:`has_time_based_updater`
"""
return [
updater
for updater in self.updaters
if "dt" in inspect.signature(updater).parameters
]
rv: list[_TimeBasedUpdater] = []
for updater in self.updaters:
if "dt" in inspect.signature(updater).parameters:
time_based_updater = cast(_TimeBasedUpdater, updater)
rv.append(time_based_updater)
return rv
def has_time_based_updater(self) -> bool:
"""Test if ``self`` has a time based updater.
@ -981,7 +993,7 @@ class Mobject:
"dt" in inspect.signature(updater).parameters for updater in self.updaters
)
def get_updaters(self) -> list[Updater]:
def get_updaters(self) -> list[_Updater]:
"""Return all updaters.
Returns
@ -997,12 +1009,12 @@ class Mobject:
"""
return self.updaters
def get_family_updaters(self) -> list[Updater]:
def get_family_updaters(self) -> list[_Updater]:
return list(it.chain(*(sm.get_updaters() for sm in self.get_family())))
def add_updater(
self,
update_function: Updater,
update_function: _Updater,
index: int | None = None,
call_updater: bool = False,
) -> Self:
@ -1076,12 +1088,15 @@ class Mobject:
if call_updater:
parameters = inspect.signature(update_function).parameters
if "dt" in parameters:
update_function(self, 0)
time_based_updater = cast(_TimeBasedUpdater, update_function)
time_based_updater(self, 0)
else:
update_function(self)
non_time_based_updater = cast(_NonTimeBasedUpdater, update_function)
non_time_based_updater(self)
return self
def remove_updater(self, update_function: Updater) -> Self:
def remove_updater(self, update_function: _Updater) -> Self:
"""Remove an updater.
If the same updater is applied multiple times, every instance gets removed.
@ -1328,6 +1343,7 @@ class Mobject:
*,
about_point: Point3DLike | None = None,
about_edge: Vector3DLike | None = None,
**kwargs: Any,
) -> Self:
"""Rotates the :class:`~.Mobject` around a specified axis and point.
@ -1516,10 +1532,10 @@ class Mobject:
self.play(t.animate.set_value(TAU), run_time=3)
"""
def R3_func(point):
def R3_func(point: Point3D) -> Point3D:
x, y, z = point
xy_complex = function(complex(x, y))
return [xy_complex.real, xy_complex.imag, z]
return np.array([xy_complex.real, xy_complex.imag, z])
return self.apply_function(
R3_func, about_point=about_point, about_edge=about_edge
@ -1533,7 +1549,7 @@ class Mobject:
def repeat(self, count: int) -> Self:
"""This can make transition animations nicer"""
def repeat_array(array):
def repeat_array(array: Point3D_Array) -> Point3D_Array:
return reduce(lambda a1, a2: np.append(a1, a2, axis=0), [array] * count)
for mob in self.family_members_with_points():
@ -1563,7 +1579,7 @@ class Mobject:
mob.points += about_point
return self
def pose_at_angle(self, **kwargs):
def pose_at_angle(self, **kwargs: Any) -> Self:
self.rotate(TAU / 14, RIGHT + UP, **kwargs)
return self
@ -1709,7 +1725,7 @@ class Mobject:
self.shift((target_point - point_to_align + buff * np_direction) * coor_mask)
return self
def shift_onto_screen(self, **kwargs) -> Self:
def shift_onto_screen(self, **kwargs: Any) -> Self:
space_lengths = [config["frame_x_radius"], config["frame_y_radius"]]
for vect in UP, DOWN, LEFT, RIGHT:
dim = np.argmax(np.abs(vect))
@ -1720,20 +1736,21 @@ class Mobject:
self.to_edge(vect, **kwargs)
return self
def is_off_screen(self):
def is_off_screen(self) -> bool:
if self.get_left()[0] > config["frame_x_radius"]:
return True
if self.get_right()[0] < -config["frame_x_radius"]:
return True
if self.get_bottom()[1] > config["frame_y_radius"]:
return True
return self.get_top()[1] < -config["frame_y_radius"]
rv: bool = self.get_top()[1] < -config["frame_y_radius"]
return rv
def stretch_about_point(self, factor: float, dim: int, point: Point3DLike) -> Self:
return self.stretch(factor, dim, about_point=point)
def rescale_to_fit(
self, length: float, dim: int, stretch: bool = False, **kwargs
self, length: float, dim: int, stretch: bool = False, **kwargs: Any
) -> Self:
old_length = self.length_over_dim(dim)
if old_length == 0:
@ -1744,7 +1761,7 @@ class Mobject:
self.scale(length / old_length, **kwargs)
return self
def scale_to_fit_width(self, width: float, **kwargs) -> Self:
def scale_to_fit_width(self, width: float, **kwargs: Any) -> Self:
"""Scales the :class:`~.Mobject` to fit a width while keeping height/depth proportional.
Returns
@ -1769,7 +1786,7 @@ class Mobject:
"""
return self.rescale_to_fit(width, 0, stretch=False, **kwargs)
def stretch_to_fit_width(self, width: float, **kwargs) -> Self:
def stretch_to_fit_width(self, width: float, **kwargs: Any) -> Self:
"""Stretches the :class:`~.Mobject` to fit a width, not keeping height/depth proportional.
Returns
@ -1794,7 +1811,7 @@ class Mobject:
"""
return self.rescale_to_fit(width, 0, stretch=True, **kwargs)
def scale_to_fit_height(self, height: float, **kwargs) -> Self:
def scale_to_fit_height(self, height: float, **kwargs: Any) -> Self:
"""Scales the :class:`~.Mobject` to fit a height while keeping width/depth proportional.
Returns
@ -1819,7 +1836,7 @@ class Mobject:
"""
return self.rescale_to_fit(height, 1, stretch=False, **kwargs)
def stretch_to_fit_height(self, height: float, **kwargs) -> Self:
def stretch_to_fit_height(self, height: float, **kwargs: Any) -> Self:
"""Stretches the :class:`~.Mobject` to fit a height, not keeping width/depth proportional.
Returns
@ -1844,15 +1861,17 @@ class Mobject:
"""
return self.rescale_to_fit(height, 1, stretch=True, **kwargs)
def scale_to_fit_depth(self, depth: float, **kwargs) -> Self:
def scale_to_fit_depth(self, depth: float, **kwargs: Any) -> Self:
"""Scales the :class:`~.Mobject` to fit a depth while keeping width/height proportional."""
return self.rescale_to_fit(depth, 2, stretch=False, **kwargs)
def stretch_to_fit_depth(self, depth: float, **kwargs) -> Self:
def stretch_to_fit_depth(self, depth: float, **kwargs: Any) -> Self:
"""Stretches the :class:`~.Mobject` to fit a depth, not keeping width/height proportional."""
return self.rescale_to_fit(depth, 2, stretch=True, **kwargs)
def set_coord(self, value, dim: int, direction: Vector3DLike = ORIGIN) -> Self:
def set_coord(
self, value: float, dim: int, direction: Vector3DLike = ORIGIN
) -> Self:
curr = self.get_coord(dim, direction)
shift_vect = np.zeros(self.dim)
shift_vect[dim] = value - curr
@ -1871,7 +1890,7 @@ class Mobject:
"""Set z value of the center of the :class:`~.Mobject` (``int`` or ``float``)"""
return self.set_coord(z, 2, direction)
def space_out_submobjects(self, factor: float = 1.5, **kwargs) -> Self:
def space_out_submobjects(self, factor: float = 1.5, **kwargs: Any) -> Self:
self.scale(factor, **kwargs)
for submob in self.submobjects:
submob.scale(1.0 / factor)
@ -1950,7 +1969,10 @@ class Mobject:
# Background rectangle
def add_background_rectangle(
self, color: ParsableManimColor | None = None, opacity: float = 0.75, **kwargs
self,
color: ParsableManimColor | None = None,
opacity: float = 0.75,
**kwargs: Any,
) -> Self:
"""Add a BackgroundRectangle as submobject.
@ -1989,12 +2011,14 @@ class Mobject:
self.add_to_back(self.background_rectangle)
return self
def add_background_rectangle_to_submobjects(self, **kwargs) -> Self:
def add_background_rectangle_to_submobjects(self, **kwargs: Any) -> Self:
for submobject in self.submobjects:
submobject.add_background_rectangle(**kwargs)
return self
def add_background_rectangle_to_family_members_with_points(self, **kwargs) -> Self:
def add_background_rectangle_to_family_members_with_points(
self, **kwargs: Any
) -> Self:
for mob in self.family_members_with_points():
mob.add_background_rectangle(**kwargs)
return self
@ -2002,7 +2026,10 @@ class Mobject:
# Color functions
def set_color(
self, color: ParsableManimColor = PURE_YELLOW, family: bool = True
self,
color: ParsableManimColor = PURE_YELLOW,
alpha: Any = None,
family: bool = True,
) -> Self:
"""Condition is function which takes in one arguments, (x, y, z).
Here it just recurses to submobjects, but in subclasses this
@ -2044,7 +2071,7 @@ class Mobject:
)
return self
def set_submobject_colors_by_gradient(self, *colors: Iterable[ParsableManimColor]):
def set_submobject_colors_by_gradient(self, *colors: ParsableManimColor) -> Self:
if len(colors) == 0:
raise ValueError("Need at least one color")
elif len(colors) == 1:
@ -2070,7 +2097,9 @@ class Mobject:
for mob in self.family_members_with_points():
t = np.linalg.norm(mob.get_center() - center) / radius
t = min(t, 1)
mob_color = interpolate_color(inner_color, outer_color, t)
mob_color = interpolate_color(
ManimColor(inner_color), ManimColor(outer_color), t
)
mob.set_color(mob_color, family=False)
return self
@ -2083,7 +2112,7 @@ class Mobject:
self, color: ParsableManimColor, alpha: float, family: bool = True
) -> Self:
if self.get_num_points() > 0:
new_color = interpolate_color(self.get_color(), color, alpha)
new_color = interpolate_color(self.get_color(), ManimColor(color), alpha)
self.set_color(new_color, family=False)
if family:
for submob in self.submobjects:
@ -2123,12 +2152,14 @@ class Mobject:
def restore(self) -> Self:
"""Restores the state that was previously saved with :meth:`~.Mobject.save_state`."""
if not hasattr(self, "saved_state") or self.save_state is None:
if not hasattr(self, "saved_state") or self.saved_state is None:
raise Exception("Trying to restore without having saved")
self.become(self.saved_state)
return self
def reduce_across_dimension(self, reduce_func: Callable, dim: int):
def reduce_across_dimension(
self, reduce_func: Callable[[Iterable[float]], float], dim: int
) -> float:
"""Find the min or max value from a dimension across all points in this and submobjects."""
assert dim >= 0
assert dim <= 2
@ -2148,9 +2179,10 @@ class Mobject:
for mobj in self.submobjects:
value = mobj.reduce_across_dimension(reduce_func, dim)
rv = value if rv is None else reduce_func([value, rv])
assert rv is not None
return rv
def nonempty_submobjects(self) -> list[Self]:
def nonempty_submobjects(self) -> Sequence[Mobject]:
return [
submob
for submob in self.submobjects
@ -2194,11 +2226,14 @@ class Mobject:
)
values = np_points[:, dim]
if key < 0:
return np.min(values)
rv: float = np.min(values)
return rv
elif key == 0:
return (np.min(values) + np.max(values)) / 2
rv = (np.min(values) + np.max(values)) / 2
return rv
else:
return np.max(values)
rv = np.max(values)
return rv
def get_critical_point(self, direction: Vector3DLike) -> Point3D:
"""Picture a box bounding the :class:`~.Mobject`. Such a box has
@ -2223,7 +2258,7 @@ class Mobject:
result[dim] = self.get_extremum_along_dim(
all_points,
dim=dim,
key=direction[dim],
key=np.array(direction[dim]),
)
return result
@ -2298,14 +2333,16 @@ class Mobject:
def length_over_dim(self, dim: int) -> float:
"""Measure the length of an :class:`~.Mobject` in a certain direction."""
return self.reduce_across_dimension(
max_coord: float = self.reduce_across_dimension(
max,
dim,
) - self.reduce_across_dimension(min, dim)
)
min_coord: float = self.reduce_across_dimension(min, dim)
return max_coord - min_coord
def get_coord(self, dim: int, direction: Vector3DLike = ORIGIN) -> float:
"""Meant to generalize ``get_x``, ``get_y`` and ``get_z``"""
return self.get_extremum_along_dim(dim=dim, key=direction[dim])
return self.get_extremum_along_dim(dim=dim, key=np.array(direction)[dim])
def get_x(self, direction: Vector3DLike = ORIGIN) -> float:
"""Returns x Point3D of the center of the :class:`~.Mobject` as ``float``"""
@ -2369,19 +2406,19 @@ class Mobject:
"""Match the color with the color of another :class:`~.Mobject`."""
return self.set_color(mobject.get_color())
def match_dim_size(self, mobject: Mobject, dim: int, **kwargs) -> Self:
def match_dim_size(self, mobject: Mobject, dim: int, **kwargs: Any) -> Self:
"""Match the specified dimension with the dimension of another :class:`~.Mobject`."""
return self.rescale_to_fit(mobject.length_over_dim(dim), dim, **kwargs)
def match_width(self, mobject: Mobject, **kwargs) -> Self:
def match_width(self, mobject: Mobject, **kwargs: Any) -> Self:
"""Match the width with the width of another :class:`~.Mobject`."""
return self.match_dim_size(mobject, 0, **kwargs)
def match_height(self, mobject: Mobject, **kwargs) -> Self:
def match_height(self, mobject: Mobject, **kwargs: Any) -> Self:
"""Match the height with the height of another :class:`~.Mobject`."""
return self.match_dim_size(mobject, 1, **kwargs)
def match_depth(self, mobject: Mobject, **kwargs) -> Self:
def match_depth(self, mobject: Mobject, **kwargs: Any) -> Self:
"""Match the depth with the depth of another :class:`~.Mobject`."""
return self.match_dim_size(mobject, 2, **kwargs)
@ -2395,15 +2432,15 @@ class Mobject:
direction=direction,
)
def match_x(self, mobject: Mobject, direction=ORIGIN) -> Self:
def match_x(self, mobject: Mobject, direction: Vector3DLike = ORIGIN) -> Self:
"""Match x coord. to the x coord. of another :class:`~.Mobject`."""
return self.match_coord(mobject, 0, direction)
def match_y(self, mobject: Mobject, direction=ORIGIN) -> Self:
def match_y(self, mobject: Mobject, direction: Vector3DLike = ORIGIN) -> Self:
"""Match y coord. to the x coord. of another :class:`~.Mobject`."""
return self.match_coord(mobject, 1, direction)
def match_z(self, mobject: Mobject, direction=ORIGIN) -> Self:
def match_z(self, mobject: Mobject, direction: Vector3DLike = ORIGIN) -> Self:
"""Match z coord. to the x coord. of another :class:`~.Mobject`."""
return self.match_coord(mobject, 2, direction)
@ -2430,14 +2467,15 @@ class Mobject:
# Family matters
def __getitem__(self, value):
def __getitem__(self, value: Any) -> Mobject | Group:
self_list = self.split()
if isinstance(value, slice):
GroupClass = self.get_group_class()
return GroupClass(*self_list.__getitem__(value))
return self_list.__getitem__(value)
rv: Mobject | Group = self_list.__getitem__(value)
return rv
def __iter__(self):
def __iter__(self) -> Iterator[Mobject]:
return iter(self.split())
def __len__(self) -> int:
@ -2451,11 +2489,11 @@ class Mobject:
"""Return the base class of this mobject type."""
return Mobject
def split(self) -> list[Self]:
result = [self] if len(self.points) > 0 else []
def split(self) -> list[Mobject]:
result: list[Mobject] = [self] if len(self.points) > 0 else []
return result + self.submobjects
def get_family(self, recurse: bool = True) -> list[Self]:
def get_family(self, recurse: bool = True) -> list[Mobject]:
"""Lists all mobjects in the hierarchy (family) of the given mobject,
including the mobject itself and all its submobjects recursively.
@ -2489,7 +2527,7 @@ class Mobject:
all_mobjects = [self] + list(it.chain(*sub_families))
return remove_list_redundancies(all_mobjects)
def family_members_with_points(self) -> list[Self]:
def family_members_with_points(self) -> list[Mobject]:
"""Filters the list of family members (generated by :meth:`.get_family`) to include only mobjects with points.
Returns
@ -2520,7 +2558,7 @@ class Mobject:
direction: Vector3DLike = RIGHT,
buff: float = DEFAULT_MOBJECT_TO_MOBJECT_BUFFER,
center: bool = True,
**kwargs,
**kwargs: Any,
) -> Self:
"""Sorts :class:`~.Mobject` next to each other on screen.
@ -2556,7 +2594,7 @@ class Mobject:
row_heights: Iterable[float | None] | None = None,
col_widths: Iterable[float | None] | None = None,
flow_order: str = "rd",
**kwargs,
**kwargs: Any,
) -> Self:
"""Arrange submobjects in a grid.
@ -2650,13 +2688,18 @@ class Mobject:
start_pos = self.get_center()
# get cols / rows values if given (implicitly)
def init_size(num, alignments, sizes):
def init_size(
num: int | None,
alignments: str | None,
sizes: Iterable[float | None] | None,
) -> int | None:
if num is not None:
return num
if alignments is not None:
return len(alignments)
if sizes is not None:
return len(sizes)
return len(list(sizes))
return None
cols = init_size(cols, col_alignments, col_widths)
rows = init_size(rows, row_alignments, row_heights)
@ -2667,8 +2710,9 @@ class Mobject:
# make the grid as close to quadratic as possible.
# choosing cols first can results in cols>rows.
# This is favored over rows>cols since in general
# the sceene is wider than high.
# the scene is wider than high.
if rows is None:
assert isinstance(cols, int)
rows = math.ceil(len(mobs) / cols)
if cols is None:
cols = math.ceil(len(mobs) / rows)
@ -2683,25 +2727,29 @@ class Mobject:
buff_x = buff_y = buff
# Initialize alignments correctly
def init_alignments(alignments, num, mapping, name, dir_):
def init_alignments(
alignments: str | None,
num: int,
char_to_direction: dict[str, Vector3D],
name: str,
dir_: Vector3D,
) -> list[Vector3D]:
if alignments is None:
# Use cell_alignment as fallback
return [cell_alignment * dir_] * num
if len(alignments) != num:
raise ValueError(f"{name}_alignments has a mismatching size.")
alignments = list(alignments)
for i in range(num):
alignments[i] = mapping[alignments[i]]
return alignments
alignment_directions = [char_to_direction[char] for char in alignments]
return alignment_directions
row_alignments = init_alignments(
row_alignment_directions = init_alignments(
row_alignments,
rows,
{"u": UP, "c": ORIGIN, "d": DOWN},
"row",
RIGHT,
)
col_alignments = init_alignments(
col_alignment_directions = init_alignments(
col_alignments,
cols,
{"l": LEFT, "c": ORIGIN, "r": RIGHT},
@ -2710,7 +2758,7 @@ class Mobject:
)
# Now row_alignment[r] + col_alignment[c] is the alignment in cell [r][c]
mapper = {
mapper: dict[str, Callable[[int, int], int]] = {
"dr": lambda r, c: (rows - r - 1) + c * rows,
"dl": lambda r, c: (rows - r - 1) + (cols - c - 1) * rows,
"ur": lambda r, c: r + c * rows,
@ -2724,18 +2772,14 @@ class Mobject:
raise ValueError(
'flow_order must be one of the following values: "dr", "rd", "ld" "dl", "ru", "ur", "lu", "ul".',
)
flow_order = mapper[flow_order]
get_mob_index_by_position = mapper[flow_order]
# Reverse row_alignments and row_heights. Necessary since the
# Reverse row_alignment_directions and row_heights. Necessary since the
# grid filling is handled bottom up for simplicity reasons.
def reverse(maybe_list):
if maybe_list is not None:
maybe_list = list(maybe_list)
maybe_list.reverse()
return maybe_list
row_alignments = reverse(row_alignments)
row_heights = reverse(row_heights)
row_alignment_directions.reverse()
row_heights_list = list(row_heights) if row_heights is not None else []
row_heights_list.reverse()
col_widths_list = list(col_widths) if col_widths is not None else []
placeholder = Mobject()
# Used to fill up the grid temporarily, doesn't get added to the scene.
@ -2743,7 +2787,10 @@ class Mobject:
# properties of 0.
mobs.extend([placeholder] * (rows * cols - len(mobs)))
grid = [[mobs[flow_order(r, c)] for c in range(cols)] for r in range(rows)]
grid = [
[mobs[get_mob_index_by_position(r, c)] for c in range(cols)]
for r in range(rows)
]
measured_heigths = [
max(grid[r][c].height for c in range(cols)) for r in range(rows)
@ -2753,24 +2800,29 @@ class Mobject:
]
# Initialize row_heights / col_widths correctly using measurements as fallback
def init_sizes(sizes, num, measures, name):
if sizes is None:
def init_sizes(
sizes: list[float | None] | None, num: int, measures: list[float], name: str
) -> list[float]:
if sizes is None or len(sizes) == 0:
sizes = [None] * num
if len(sizes) != num:
raise ValueError(f"{name} has a mismatching size.")
return [
sizes[i] if sizes[i] is not None else measures[i] for i in range(num)
size if size is not None else measure
for size, measure in zip(sizes, measures, strict=False)
]
heights = init_sizes(row_heights, rows, measured_heigths, "row_heights")
widths = init_sizes(col_widths, cols, measured_widths, "col_widths")
heights = init_sizes(row_heights_list, rows, measured_heigths, "row_heights")
widths = init_sizes(col_widths_list, cols, measured_widths, "col_widths")
x, y = 0, 0
x, y = 0.0, 0.0
for r in range(rows):
x = 0
for c in range(cols):
if grid[r][c] is not placeholder:
alignment = row_alignments[r] + col_alignments[c]
alignment = (
row_alignment_directions[r] + col_alignment_directions[c]
)
line = Line(
x * RIGHT + y * UP,
(x + widths[c]) * RIGHT + (y + heights[r]) * UP,
@ -2834,7 +2886,7 @@ class Mobject:
self.submobjects.reverse()
# Just here to keep from breaking old scenes.
def arrange_submobjects(self, *args, **kwargs) -> Self:
def arrange_submobjects(self, *args: Any, **kwargs: Any) -> Self:
"""Arrange the position of :attr:`submobjects` with a small buffer.
Examples
@ -2855,11 +2907,11 @@ class Mobject:
"""
return self.arrange(*args, **kwargs)
def sort_submobjects(self, *args, **kwargs) -> Self:
def sort_submobjects(self, *args: Any, **kwargs: Any) -> Self:
"""Sort the :attr:`submobjects`"""
return self.sort(*args, **kwargs)
def shuffle_submobjects(self, *args, **kwargs) -> None:
def shuffle_submobjects(self, *args: Any, **kwargs: Any) -> None:
"""Shuffles the order of :attr:`submobjects`
Examples
@ -2927,7 +2979,7 @@ class Mobject:
for m1, m2 in zip(self.submobjects, mobject.submobjects, strict=True):
m1.align_data(m2)
def get_point_mobject(self, center=None):
def get_point_mobject(self, center: Point3DLike | None = None) -> Point:
"""The simplest :class:`~.Mobject` to be transformed to or from self.
Should by a point of the appropriate type
"""
@ -2943,7 +2995,7 @@ class Mobject:
mobject.align_points_with_larger(self)
return self
def align_points_with_larger(self, larger_mobject: Mobject):
def align_points_with_larger(self, larger_mobject: Mobject) -> None:
raise NotImplementedError("Please override in a child class.")
def align_submobjects(self, mobject: Mobject) -> Self:
@ -2955,7 +3007,7 @@ class Mobject:
mob2.add_n_more_submobjects(max(0, n1 - n2))
return self
def null_point_align(self, mobject: Mobject):
def null_point_align(self, mobject: Mobject) -> Self:
"""If a :class:`~.Mobject` with points is being aligned to
one without, treat both as groups, and push
the one with points into its own submobjects
@ -3000,7 +3052,7 @@ class Mobject:
self.submobjects = new_submobs
return self
def repeat_submobject(self, submob: Mobject) -> Self:
def repeat_submobject(self, submob: Mobject) -> Mobject:
return submob.copy()
def interpolate(
@ -3076,7 +3128,9 @@ class Mobject:
self.interpolate_color(mobject1, mobject2, alpha)
return self
def interpolate_color(self, mobject1: Mobject, mobject2: Mobject, alpha: float):
def interpolate_color(
self, mobject1: Mobject, mobject2: Mobject, alpha: float
) -> None:
raise NotImplementedError("Please override in a child class.")
def become(
@ -3307,25 +3361,25 @@ class Group(Mobject, metaclass=ConvertToOpenGL):
be added to the group.
"""
def __init__(self, *mobjects, **kwargs) -> None:
def __init__(self, *mobjects: Any, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.add(*mobjects)
class _AnimationBuilder:
def __init__(self, mobject) -> None:
def __init__(self, mobject: Mobject) -> None:
self.mobject = mobject
self.mobject.generate_target()
self.overridden_animation = None
self.overridden_animation: Animation | None = None
self.is_chaining = False
self.methods: list[MethodWithArgs] = []
# Whether animation args can be passed
self.cannot_pass_args = False
self.anim_args = {}
self.anim_args: dict[str, Any] = {}
def __call__(self, **kwargs) -> Self:
def __call__(self, **kwargs: Any) -> Self:
if self.cannot_pass_args:
raise ValueError(
"Animation arguments must be passed before accessing methods and can only be passed once",
@ -3336,7 +3390,7 @@ class _AnimationBuilder:
return self
def __getattr__(self, method_name) -> types.MethodType:
def __getattr__(self, method_name: str) -> Callable[..., _AnimationBuilder]:
method = getattr(self.mobject.target, method_name)
has_overridden_animation = hasattr(method, "_override_animate")
@ -3345,7 +3399,7 @@ class _AnimationBuilder:
"Method chaining is currently not supported for overridden animations",
)
def update_target(*method_args, **method_kwargs):
def update_target(*method_args: Any, **method_kwargs: Any) -> _AnimationBuilder:
if has_overridden_animation:
self.overridden_animation = method._override_animate(
self.mobject,
@ -3364,9 +3418,7 @@ class _AnimationBuilder:
return update_target
def build(self) -> Animation:
from ..animation.transform import ( # is this to prevent circular import?
_MethodAnimation,
)
from ..animation.transform import _MethodAnimation
anim = self.overridden_animation or _MethodAnimation(self.mobject, self.methods)
@ -3382,9 +3434,9 @@ class _UpdaterBuilder:
def __init__(self, mobject: Mobject):
self._mobject = mobject
def __getattr__(self, name: str, /) -> Callable[..., Self]:
def __getattr__(self, name: str, /) -> Callable[..., _UpdaterBuilder]:
# just return a function that will add the updater
def add_updater(*method_args, **method_kwargs) -> Self:
def add_updater(*method_args: Any, **method_kwargs: Any) -> _UpdaterBuilder:
self._mobject.add_updater(
lambda m: getattr(m, name)(*method_args, **method_kwargs),
call_updater=True,
@ -3394,7 +3446,9 @@ class _UpdaterBuilder:
return add_updater
def override_animate(method) -> types.FunctionType:
def override_animate(
method: types.MethodType,
) -> Callable[[types.MethodType], types.MethodType]:
r"""Decorator for overriding method animations.
This allows to specify a method (returning an :class:`~.Animation`)
@ -3445,9 +3499,11 @@ def override_animate(method) -> types.FunctionType:
self.wait()
"""
temp_method = cast(_AnimationBuilder, method)
def decorator(animation_method):
method._override_animate = animation_method
def decorator(animation_method: types.MethodType) -> types.MethodType:
# error: "Callable[..., Animation]" has no attribute "_override_animate" [attr-defined]
temp_method._override_animate = animation_method # type: ignore[attr-defined]
return animation_method
return decorator

View file

@ -591,7 +591,7 @@ class MathTex(SingleStringMathTex):
self.id_to_vgroup_dict[match[1]].set_color(color)
return self
def index_of_part(self, part: MathTex) -> int:
def index_of_part(self, part: VMobject) -> int:
split_self = self.split()
if part not in split_self:
raise ValueError("Trying to get index of part not in MathTex")

View file

@ -166,9 +166,12 @@ class Paragraph(VGroup):
lines_str_list = lines_str.split("\n")
self.chars = self._gen_chars(lines_str_list)
self.lines = [list(self.chars), [self.alignment] * len(self.chars)]
self.lines_initial_positions = [line.get_center() for line in self.lines[0]]
self.add(*self.lines[0])
# TODO: If possible get rid of self.lines_chars, as it seems to be a
# listified duplicate of self.chars.
self.lines_chars = list(self.chars)
self.lines_alignments = [self.alignment] * len(self.chars)
self.lines_initial_positions = [line.get_center() for line in self.lines_chars]
self.add(*self.lines_chars)
self.move_to(np.array([0, 0, 0]))
if self.alignment:
self._set_all_lines_alignments(self.alignment)
@ -221,7 +224,7 @@ class Paragraph(VGroup):
alignment
Defines the alignment of paragraph. Possible values are "left", "right", "center".
"""
for line_no in range(len(self.lines[0])):
for line_no in range(len(self.lines_chars)):
self._change_alignment_for_a_line(alignment, line_no)
return self
@ -240,8 +243,8 @@ class Paragraph(VGroup):
def _set_all_lines_to_initial_positions(self) -> Paragraph:
"""Set all lines to their initial positions."""
self.lines[1] = [None] * len(self.lines[0])
for line_no in range(len(self.lines[0])):
self.lines_alignments = [None] * len(self.lines_chars)
for line_no in range(len(self.lines_chars)):
self[line_no].move_to(
self.get_center() + self.lines_initial_positions[line_no],
)
@ -255,7 +258,7 @@ class Paragraph(VGroup):
line_no
Defines the line number for which we want to set given alignment.
"""
self.lines[1][line_no] = None
self.lines_alignments[line_no] = None
self[line_no].move_to(self.get_center() + self.lines_initial_positions[line_no])
return self
@ -269,12 +272,12 @@ class Paragraph(VGroup):
line_no
Defines the line number for which we want to set given alignment.
"""
self.lines[1][line_no] = alignment
if self.lines[1][line_no] == "center":
self.lines_alignments[line_no] = alignment
if self.lines_alignments[line_no] == "center":
self[line_no].move_to(
np.array([self.get_center()[0], self[line_no].get_center()[1], 0]),
)
elif self.lines[1][line_no] == "right":
elif self.lines_alignments[line_no] == "right":
self[line_no].move_to(
np.array(
[
@ -284,7 +287,7 @@ class Paragraph(VGroup):
],
),
)
elif self.lines[1][line_no] == "left":
elif self.lines_alignments[line_no] == "left":
self[line_no].move_to(
np.array(
[

View file

@ -149,6 +149,7 @@ class Surface(VGroup, metaclass=ConvertToOpenGL):
self.pre_function_handle_to_anchor_scale_factor = (
pre_function_handle_to_anchor_scale_factor
)
self.list_of_faces: list[ThreeDVMobject] = []
self._func = func
self._setup_in_uv_space()
self.apply_function(lambda p: func(p[0], p[1]))
@ -172,6 +173,7 @@ class Surface(VGroup, metaclass=ConvertToOpenGL):
def _setup_in_uv_space(self) -> None:
u_values, v_values = self._get_u_values_and_v_values()
faces = VGroup()
self.list_of_faces = []
for i in range(len(u_values) - 1):
for j in range(len(v_values) - 1):
u1, u2 = u_values[i : i + 2]
@ -193,6 +195,7 @@ class Surface(VGroup, metaclass=ConvertToOpenGL):
face.u2 = u2
face.v1 = v1
face.v2 = v2
self.list_of_faces.append(face)
faces.set_fill(color=self.fill_color, opacity=self.fill_opacity)
faces.set_stroke(
color=self.stroke_color,
@ -223,7 +226,7 @@ class Surface(VGroup, metaclass=ConvertToOpenGL):
The parametric surface with an alternating pattern.
"""
n_colors = len(colors)
for face in self:
for face in self.list_of_faces:
c_index = (face.u_index + face.v_index) % n_colors
face.set_fill(colors[c_index], opacity=opacity)
return self

View file

@ -68,7 +68,7 @@ class AbstractImageMobject(Mobject):
def get_pixel_array(self) -> PixelArray:
raise NotImplementedError()
def set_color( # type: ignore[override]
def set_color(
self,
color: ParsableManimColor = YELLOW_C,
alpha: Any = None,
@ -217,7 +217,7 @@ class ImageMobject(AbstractImageMobject):
"""A simple getter method."""
return self.pixel_array
def set_color( # type: ignore[override]
def set_color(
self,
color: ParsableManimColor = YELLOW_C,
alpha: Any = None,

View file

@ -47,6 +47,7 @@ from manim.utils.iterables import (
from manim.utils.space_ops import rotate_vector, shoelace_direction
if TYPE_CHECKING:
from collections.abc import Iterator
from typing import Self
import numpy.typing as npt
@ -103,6 +104,7 @@ class VMobject(Mobject):
"""
sheen_factor = 0.0
target: VMobject
def __init__(
self,
@ -172,6 +174,9 @@ class VMobject(Mobject):
def _assert_valid_submobjects(self, submobjects: Iterable[VMobject]) -> Self:
return self._assert_valid_submobjects_internal(submobjects, VMobject)
def __iter__(self) -> Iterator[VMobject]:
return iter(self.split())
# OpenGL compatibility
@property
def n_points_per_curve(self) -> int:
@ -630,6 +635,17 @@ class VMobject(Mobject):
color: ManimColor = property(get_color, set_color)
def nonempty_submobjects(self) -> Sequence[VMobject]:
return [
submob
for submob in self.submobjects
if len(submob.submobjects) != 0 or len(submob.points) != 0
]
def split(self) -> list[VMobject]:
result: list[VMobject] = [self] if len(self.points) > 0 else []
return result + self.submobjects
def set_sheen_direction(self, direction: Vector3DLike, family: bool = True) -> Self:
"""Sets the direction of the applied sheen.
@ -2303,6 +2319,11 @@ class VGroup(VMobject, metaclass=ConvertToOpenGL):
self._assert_valid_submobjects(tuplify(value))
self.submobjects[key] = value
def __getitem__(self, key: int | slice) -> VMobject:
if isinstance(key, slice):
return VGroup(self.submobjects[key])
return self.submobjects[key]
class VDict(VMobject, metaclass=ConvertToOpenGL):
"""A VGroup-like class, also offering submobject access by

View file

@ -281,15 +281,19 @@ class VectorScene(Scene):
color (str),
label_scale_factor=VECTOR_LABEL_SCALE_FACTOR (int, float),
"""
i_hat, j_hat = self.get_basis_vectors()
i_hat = self.get_basis_vectors().submobjects[0]
j_hat = self.get_basis_vectors().submobjects[1]
return VGroup(
*(
self.get_vector_label(
vect, label, color=color, label_scale_factor=1, **kwargs
)
for vect, label, color in [
(i_hat, "\\hat{\\imath}", X_COLOR),
(j_hat, "\\hat{\\jmath}", Y_COLOR),
# Casting i_hat and j_hat to Vector, as the VGroup from
# self.get_basis_vectors() contains two vectors, but the
# type checker is currently not aware of that.
(cast(Vector, i_hat), "\\hat{\\imath}", X_COLOR),
(cast(Vector, j_hat), "\\hat{\\jmath}", Y_COLOR),
]
)
)
@ -517,7 +521,9 @@ class VectorScene(Scene):
y_line = Line(x_line.get_end(), arrow.get_end())
x_line.set_color(X_COLOR)
y_line.set_color(Y_COLOR)
x_coord, y_coord = cast(VGroup, array.get_entries())
temp = array.get_entries()
x_coord = temp.submobjects[0]
y_coord = temp.submobjects[1]
x_coord_start = self.position_x_coordinate(x_coord.copy(), x_line, vector)
y_coord_start = self.position_y_coordinate(y_coord.copy(), y_line, vector)
brackets = array.get_brackets()

View file

@ -250,6 +250,9 @@ def deprecated(
if type(func).__name__ != "function":
deprecate_docs(func)
# The following line raises this mypy error:
# Accessing "__init__" on an instance is unsound, since instance.__init__
# could be from an incompatible subclass [misc]</pre>
func.__init__ = decorate(func.__init__, deprecate)
return func

View file

@ -85,9 +85,6 @@ ignore_errors = True
[mypy-manim.mobject.logo]
ignore_errors = True
[mypy-manim.mobject.mobject]
ignore_errors = True
[mypy-manim.mobject.opengl.opengl_point_cloud_mobject]
ignore_errors = True
@ -100,6 +97,9 @@ ignore_errors = True
[mypy-manim.mobject.table]
ignore_errors = True
[mypy-manim.mobject.types.point_cloud_mobject]
ignore_errors = True
[mypy-manim.mobject.types.vectorized_mobject]
ignore_errors = True