diff --git a/manim/animation/indication.py b/manim/animation/indication.py index 6008573119..fe476ee610 100644 --- a/manim/animation/indication.py +++ b/manim/animation/indication.py @@ -263,6 +263,7 @@ def create_lines(self) -> VGroup: def create_line_anims(self) -> Iterable[ShowPassingFlash]: return [ ShowPassingFlash( + # error: Argument 1 to "ShowPassingFlash" has incompatible type "Mobject"; expected "VMobject" [arg-type] line, time_width=self.time_width, run_time=self.run_time, diff --git a/manim/mobject/geometry/line.py b/manim/mobject/geometry/line.py index 9b0553d1be..1be01a90e4 100644 --- a/manim/mobject/geometry/line.py +++ b/manim/mobject/geometry/line.py @@ -14,14 +14,14 @@ "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 @@ def scale(self, factor: float, scale_tips: bool = False, **kwargs: Any) -> Self: 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: diff --git a/manim/mobject/graphing/probability.py b/manim/mobject/graphing/probability.py index 9b88179bdc..2851d9b484 100644 --- a/manim/mobject/graphing/probability.py +++ b/manim/mobject/graphing/probability.py @@ -209,9 +209,9 @@ def add_braces_and_labels(self) -> None: 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] + val: VMobject = self.horizontal_parts[index] return val elif hasattr(self, "vertical_parts"): val = self.vertical_parts[index] diff --git a/manim/mobject/matrix.py b/manim/mobject/matrix.py index 36513e4e1d..629d06220f 100644 --- a/manim/mobject/matrix.py +++ b/manim/mobject/matrix.py @@ -46,7 +46,6 @@ def construct(self): import numpy as np from typing_extensions import Self -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 @@ -172,7 +171,7 @@ def __init__( 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: type[VMobject] | Callable[..., VMobject] = MathTex, element_to_mobject_config: dict = {}, element_alignment_corner: Sequence[float] = DR, left_bracket: str = "[", @@ -207,7 +206,7 @@ def __init__( 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: np.ndarray) -> list[list[VMobject]]: return [ [ self.element_to_mobject(item, **self.element_to_mobject_config) @@ -216,7 +215,7 @@ def _matrix_to_mob_matrix(self, matrix: np.ndarray) -> list[list[Mobject]]: 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] @@ -402,7 +401,7 @@ def add_background_to_entries(self) -> Self: 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 @@ -485,7 +484,7 @@ def construct(self): def __init__( self, matrix: Iterable, - element_to_mobject: type[Mobject] = DecimalNumber, + element_to_mobject: type[VMobject] = DecimalNumber, element_to_mobject_config: dict[str, Any] = {"num_decimal_places": 1}, **kwargs: Any, ): @@ -530,7 +529,7 @@ def construct(self): def __init__( self, matrix: Iterable, - element_to_mobject: type[Mobject] = Integer, + element_to_mobject: type[VMobject] = Integer, **kwargs: Any, ): """ @@ -568,7 +567,7 @@ def construct(self): def __init__( self, matrix: Iterable, - element_to_mobject: type[Mobject] | Callable[..., Mobject] = lambda m: m, + element_to_mobject: type[VMobject] | Callable[..., VMobject] = lambda m: m, **kwargs: Any, ): super().__init__(matrix, element_to_mobject=element_to_mobject, **kwargs) diff --git a/manim/mobject/mobject.py b/manim/mobject/mobject.py index e313a9e718..d53e71eb36 100644 --- a/manim/mobject/mobject.py +++ b/manim/mobject/mobject.py @@ -17,7 +17,7 @@ from collections.abc import Callable, Iterable from functools import partialmethod, reduce from pathlib import Path -from typing import TYPE_CHECKING, Any, Literal +from typing import TYPE_CHECKING, Any, Literal, cast import numpy as np @@ -41,25 +41,33 @@ from ..utils.space_ops import angle_between_vectors, normalize, rotation_matrix if TYPE_CHECKING: + from collections.abc import Iterator + from typing import Any, Callable, Literal + from typing_extensions import Self, TypeAlias + 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: @@ -82,16 +90,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__ @@ -100,15 +110,15 @@ def __init__( 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.submobjects: list[Mobject] = [] self.updaters: list[Updater] = [] self.updating_suspended = False self.color = ManimColor.parse(color) @@ -150,7 +160,7 @@ def _assert_valid_submobjects(self, submobjects: Iterable[Mobject]) -> Self: 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): @@ -246,7 +256,7 @@ def add_animation_override( ) @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 @@ -289,8 +299,11 @@ def construct(self): """ if kwargs: + # error: Cannot assign to a method [method-assign] + # error: Incompatible types in assignment (expression has type "partialmethod[Any]", variable has type "Callable[[Mobject, ManimColor | int | str | tuple[int, int, int] | tuple[float, float, float] | <6 more items> | list[ManimColor | int | str | tuple[int, int, int] | tuple[float, float, float] | <6 more items>], str | None, int, Mobject | None, float], None]") [assignment] cls.__init__ = partialmethod(cls.__init__, **kwargs) else: + # error: Cannot assign to a method [method-assign] cls.__init__ = cls._original__init__ @property @@ -393,7 +406,7 @@ def construct(self): """ return _AnimationBuilder(self) - 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 @@ -529,10 +542,10 @@ def insert(self, index: int, mobject: Mobject) -> None: 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: @@ -612,13 +625,13 @@ def remove(self, *mobjects: Mobject) -> Self: 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``. @@ -686,7 +699,7 @@ def __getattr__(self, attr: str) -> types.MethodType: # 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.", @@ -703,7 +716,7 @@ def getter(self): # 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().", @@ -754,7 +767,7 @@ def construct(self): 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 @@ -790,7 +803,7 @@ def construct(self): 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 @@ -810,7 +823,7 @@ def depth(self) -> float: 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 @@ -823,16 +836,13 @@ def apply_over_attr_arrays(self, func: MultiMappingFunction) -> Self: return self # Displaying - - def get_image(self, camera=None) -> PixelArray: + def get_image(self, camera: Camera | None = None) -> PixelArray: 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: @@ -896,9 +906,11 @@ def update(self, dt: float = 0, recursive: bool = True) -> Self: return self for updater in self.updaters: if "dt" in inspect.signature(updater).parameters: - updater(self, dt) + time_based_updater = cast(TimeBasedUpdater, updater) + time_based_updater(self, dt) else: - updater(self) + non_time_based_updater = cast(NonTimeBasedUpdater, updater) + non_time_based_updater(self) if recursive: for submob in self.submobjects: submob.update(dt, recursive) @@ -920,11 +932,12 @@ def get_time_based_updaters(self) -> list[TimeBasedUpdater]: :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. @@ -1039,9 +1052,12 @@ def construct(self): 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: @@ -1227,7 +1243,7 @@ def shift(self, *vectors: Vector3DLike) -> Self: return self - def scale(self, scale_factor: float, **kwargs) -> Self: + def scale(self, scale_factor: float, **kwargs: Any) -> Self: r"""Scale the size by a factor. Default behavior is to scale about the center of the mobject. @@ -1282,7 +1298,7 @@ def rotate( angle: float, axis: Vector3DLike = OUT, about_point: Point3DLike | None = None, - **kwargs, + **kwargs: Any, ) -> Self: """Rotates the :class:`~.Mobject` around a specified axis and point. @@ -1348,7 +1364,7 @@ def construct(self): ) return self - def flip(self, axis: Vector3DLike = UP, **kwargs) -> Self: + def flip(self, axis: Vector3DLike = UP, **kwargs: Any) -> Self: """Flips/Mirrors an mobject about its center. Examples @@ -1367,7 +1383,7 @@ def construct(self): """ return self.rotate(TAU / 2, axis, **kwargs) - def stretch(self, factor: float, dim: int, **kwargs) -> Self: + def stretch(self, factor: float, dim: int, **kwargs: Any) -> Self: def func(points: Point3D_Array) -> Point3D_Array: points[:, dim] *= factor return points @@ -1375,7 +1391,7 @@ def func(points: Point3D_Array) -> Point3D_Array: self.apply_points_function_about_point(func, **kwargs) return self - def apply_function(self, function: MappingFunction, **kwargs) -> Self: + def apply_function(self, function: MappingFunction, **kwargs: Any) -> Self: # Default to applying matrix about the origin, not mobjects center if len(kwargs) == 0: kwargs["about_point"] = ORIGIN @@ -1396,7 +1412,7 @@ def apply_function_to_submobject_positions(self, function: MappingFunction) -> S submob.apply_function_to_position(function) return self - def apply_matrix(self, matrix, **kwargs) -> Self: + def apply_matrix(self, matrix: MatrixMN, **kwargs: Any) -> Self: # Default to applying matrix about the origin, not mobjects center if ("about_point" not in kwargs) and ("about_edge" not in kwargs): kwargs["about_point"] = ORIGIN @@ -1409,7 +1425,7 @@ def apply_matrix(self, matrix, **kwargs) -> Self: return self def apply_complex_function( - self, function: Callable[[complex], complex], **kwargs + self, function: Callable[[complex], complex], **kwargs: Any ) -> Self: """Applies a complex function to a :class:`Mobject`. The x and y Point3Ds correspond to the real and imaginary parts respectively. @@ -1437,7 +1453,7 @@ def construct(self): 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] @@ -1452,7 +1468,7 @@ def reverse_points(self) -> Self: 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(): @@ -1480,7 +1496,7 @@ def apply_points_function_about_point( 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 @@ -1626,7 +1642,7 @@ def construct(self): 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)) @@ -1637,20 +1653,21 @@ def shift_onto_screen(self, **kwargs) -> Self: 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: @@ -1661,7 +1678,7 @@ def rescale_to_fit( 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 @@ -1686,7 +1703,7 @@ def scale_to_fit_width(self, width: float, **kwargs) -> Self: """ 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 @@ -1711,7 +1728,7 @@ def stretch_to_fit_width(self, width: float, **kwargs) -> Self: """ 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 @@ -1736,7 +1753,7 @@ def scale_to_fit_height(self, height: float, **kwargs) -> Self: """ 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 @@ -1761,15 +1778,17 @@ def stretch_to_fit_height(self, height: float, **kwargs) -> Self: """ 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 @@ -1788,7 +1807,7 @@ def set_z(self, z: float, direction: Vector3DLike = ORIGIN) -> Self: """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) @@ -1867,7 +1886,10 @@ def put_start_and_end_on(self, start: Point3DLike, end: Point3DLike) -> Self: # 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. @@ -1906,12 +1928,14 @@ def add_background_rectangle( 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 @@ -1961,7 +1985,7 @@ def set_colors_by_radial_gradient( ) 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: @@ -1987,7 +2011,9 @@ def set_submobject_colors_by_radial_gradient( 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 @@ -2000,7 +2026,7 @@ def fade_to( 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: @@ -2042,10 +2068,13 @@ 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: raise Exception("Trying to restore without having saved") + assert self.saved_state is not None 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[..., 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 @@ -2065,9 +2094,10 @@ def reduce_across_dimension(self, reduce_func: Callable, dim: int): 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) -> list[Mobject]: return [ submob for submob in self.submobjects @@ -2111,11 +2141,14 @@ def get_extremum_along_dim( ) 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 @@ -2140,7 +2173,7 @@ def get_critical_point(self, direction: Vector3DLike) -> Point3D: result[dim] = self.get_extremum_along_dim( all_points, dim=dim, - key=direction[dim], + key=np.array(direction[dim]), ) return result @@ -2215,14 +2248,16 @@ def get_nadir(self) -> Point3D: 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_distance: float = self.reduce_across_dimension( max, dim, - ) - self.reduce_across_dimension(min, dim) + ) + min_distance: float = self.reduce_across_dimension(min, dim) + return max_distance - min_distance 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``""" @@ -2286,19 +2321,19 @@ def match_color(self, mobject: Mobject) -> Self: """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) @@ -2312,15 +2347,15 @@ def match_coord( 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) @@ -2347,17 +2382,18 @@ def align_to( # 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): + def __len__(self) -> int: return len(self.split()) def get_group_class(self) -> type[Group]: @@ -2368,11 +2404,11 @@ def get_mobject_type_class() -> type[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. @@ -2406,7 +2442,7 @@ def get_family(self, recurse: bool = True) -> list[Self]: 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 @@ -2437,7 +2473,7 @@ def arrange( 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. @@ -2473,7 +2509,7 @@ def arrange_in_grid( 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. @@ -2567,13 +2603,18 @@ def construct(self): 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: 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)) + raise ValueError("num, alignments and sizes are all None.") cols = init_size(cols, col_alignments, col_widths) rows = init_size(rows, row_alignments, row_heights) @@ -2600,25 +2641,31 @@ def init_size(num, alignments, sizes): buff_x = buff_y = buff # Initialize alignments correctly - def init_alignments(alignments, num, mapping, name, dir_): + def init_alignments( + alignments: str | None, + num: int, + mapping: dict[str, Point3D], + name: str, + dir_: Vector3D, + ) -> list[str]: if alignments is None: # Use cell_alignment as fallback - return [cell_alignment * dir_] * num + return [np.array(cell_alignment) * dir_] * num if len(alignments) != num: raise ValueError(f"{name}_alignments has a mismatching size.") - alignments = list(alignments) + alignments_in_list = list(alignments) for i in range(num): - alignments[i] = mapping[alignments[i]] - return alignments + alignments_in_list[i] = mapping[alignments_in_list[i]] + return alignments_in_list - row_alignments = init_alignments( + row_alignments_in_list = init_alignments( row_alignments, rows, {"u": UP, "c": ORIGIN, "d": DOWN}, "row", RIGHT, ) - col_alignments = init_alignments( + col_alignments_in_list = init_alignments( col_alignments, cols, {"l": LEFT, "c": ORIGIN, "r": RIGHT}, @@ -2627,7 +2674,7 @@ def init_alignments(alignments, num, mapping, name, dir_): ) # 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, @@ -2641,18 +2688,20 @@ def init_alignments(alignments, num, mapping, name, dir_): raise ValueError( 'flow_order must be one of the following values: "dr", "rd", "ld" "dl", "ru", "ur", "lu", "ul".', ) - flow_order = mapper[flow_order] + flow_order_method = mapper[flow_order] - # Reverse row_alignments and row_heights. Necessary since the + # Reverse row_alignments_in_list and row_heights. Necessary since the # grid filling is handled bottom up for simplicity reasons. - def reverse(maybe_list): + def reverse(maybe_list: Iterable | None) -> list: if maybe_list is not None: maybe_list = list(maybe_list) maybe_list.reverse() return maybe_list + return [] - row_alignments = reverse(row_alignments) + row_alignments_in_list = reverse(row_alignments_in_list) row_heights = reverse(row_heights) + 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. @@ -2660,7 +2709,9 @@ def reverse(maybe_list): # 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[flow_order_method(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) @@ -2670,8 +2721,10 @@ def reverse(maybe_list): ] # 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 | None, num: int, measures: list, 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.") @@ -2680,14 +2733,14 @@ def init_sizes(sizes, num, measures, name): ] heights = init_sizes(row_heights, rows, measured_heigths, "row_heights") - widths = init_sizes(col_widths, cols, measured_widths, "col_widths") + 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_alignments_in_list[r] + col_alignments_in_list[c] line = Line( x * RIGHT + y * UP, (x + widths[c]) * RIGHT + (y + heights[r]) * UP, @@ -2751,7 +2804,7 @@ def construct(self): 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 @@ -2772,11 +2825,11 @@ def construct(self): """ 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 @@ -2844,7 +2897,7 @@ def align_data(self, mobject: Mobject, skip_point_alignment: bool = False) -> No for m1, m2 in zip(self.submobjects, mobject.submobjects): 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 """ @@ -2860,7 +2913,7 @@ def align_points(self, mobject: Mobject) -> Self: 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: @@ -2872,7 +2925,7 @@ def align_submobjects(self, mobject: Mobject) -> Self: 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 @@ -2918,7 +2971,7 @@ def add_n_more_submobjects(self, n: int) -> Self | None: 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( @@ -2994,7 +3047,9 @@ def construct(self): 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( @@ -3225,13 +3280,15 @@ 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: + _override_animate: Any + + def __init__(self, mobject: Mobject) -> None: self.mobject = mobject self.mobject.generate_target() @@ -3241,9 +3298,9 @@ def __init__(self, mobject) -> None: # Whether animation args can be passed self.cannot_pass_args = False - self.anim_args = {} + self.anim_args: dict = {} - 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", @@ -3254,7 +3311,7 @@ def __call__(self, **kwargs) -> Self: 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") @@ -3263,7 +3320,7 @@ def __getattr__(self, method_name) -> types.MethodType: "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, @@ -3282,9 +3339,7 @@ def update_target(*method_args, **method_kwargs): 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) @@ -3294,7 +3349,9 @@ def build(self) -> Animation: return anim -def override_animate(method) -> types.FunctionType: +def override_animate( + method: Callable[..., Animation], +) -> Callable[[Callable], Callable]: r"""Decorator for overriding method animations. This allows to specify a method (returning an :class:`~.Animation`) @@ -3345,9 +3402,11 @@ def construct(self): self.wait() """ + temp_method = cast(_AnimationBuilder, method) - def decorator(animation_method): - method._override_animate = animation_method + def decorator(animation_method: Callable) -> Callable: + # error: "Callable[..., Animation]" has no attribute "_override_animate" [attr-defined] + temp_method._override_animate = animation_method return animation_method return decorator diff --git a/manim/mobject/text/code_mobject.py b/manim/mobject/text/code_mobject.py index b3109c23e4..b418102bb4 100644 --- a/manim/mobject/text/code_mobject.py +++ b/manim/mobject/text/code_mobject.py @@ -206,7 +206,10 @@ def __init__( ) for line, color_range in zip(self.code_lines, color_ranges): for start, end, color in color_range: - line[start:end].set_color(color) + if end == start: + continue + for single_line in line[start:end]: + single_line.set_color(color) if add_line_numbers: base_paragraph_config.update({"alignment": "right"}) diff --git a/manim/mobject/text/tex_mobject.py b/manim/mobject/text/tex_mobject.py index b219694f7c..e6be263371 100644 --- a/manim/mobject/text/tex_mobject.py +++ b/manim/mobject/text/tex_mobject.py @@ -376,9 +376,9 @@ def test(tex1: str, tex2: str) -> bool: return VGroup(*(m for m in self.submobjects if test(tex, m.get_tex_string()))) - def get_part_by_tex(self, tex: str, **kwargs: Any) -> MathTex | None: + def get_part_by_tex(self, tex: str, **kwargs: Any) -> VMobject | None: all_parts = self.get_parts_by_tex(tex, **kwargs) - return all_parts[0] if all_parts else None + return all_parts.submobjects[0] if all_parts else None def set_color_by_tex( self, tex: str, color: ParsableManimColor, **kwargs: Any @@ -429,7 +429,7 @@ def set_color_by_tex_to_color_map( self.set_color_by_tex(tex, color, **kwargs) 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") @@ -521,7 +521,7 @@ def fade_all_but(self, index_or_string: int | str, opacity: float = 0.5) -> None if isinstance(arg, str): part = self.get_part_by_tex(arg) elif isinstance(arg, int): - part = self.submobjects[arg] # type: ignore[assignment] + part = self.submobjects[arg] else: raise TypeError(f"Expected int or string, got {arg}") for other_part in self.submobjects: diff --git a/manim/mobject/text/text_mobject.py b/manim/mobject/text/text_mobject.py index c35f874ed5..3819c26308 100644 --- a/manim/mobject/text/text_mobject.py +++ b/manim/mobject/text/text_mobject.py @@ -1312,19 +1312,23 @@ def add_line_to(end): if self.gradient: self.set_color_by_gradient(*self.gradient) for col in colormap: - self.chars[ + chars = self.chars[ col["start"] - col["start_offset"] : col["end"] - col["start_offset"] - col["end_offset"] - ].set_color(self._parse_color(col["color"])) + ] + for char in chars: + char.set_color(self._parse_color(col["color"])) for grad in gradientmap: - self.chars[ + chars = self.chars[ grad["start"] - grad["start_offset"] : grad["end"] - grad["start_offset"] - grad["end_offset"] - ].set_color_by_gradient( - *(self._parse_color(grad["from"]), self._parse_color(grad["to"])) - ) + ] + for char in chars: + char.set_color_by_gradient( + *(self._parse_color(grad["from"]), self._parse_color(grad["to"])) + ) # anti-aliasing if height is None and width is None: self.scale(TEXT_MOB_SCALE_FACTOR) diff --git a/manim/mobject/types/vectorized_mobject.py b/manim/mobject/types/vectorized_mobject.py index f937d8ab58..6cd53b6bdd 100644 --- a/manim/mobject/types/vectorized_mobject.py +++ b/manim/mobject/types/vectorized_mobject.py @@ -47,6 +47,8 @@ from manim.utils.space_ops import rotate_vector, shoelace_direction if TYPE_CHECKING: + from collections.abc import Iterator + import numpy.typing as npt from typing_extensions import Self @@ -102,6 +104,7 @@ class VMobject(Mobject): """ sheen_factor = 0.0 + target: VMobject def __init__( self, @@ -171,6 +174,12 @@ def __init__( 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()) + + def __len__(self) -> int: + return len(self.split()) + # OpenGL compatibility @property def n_points_per_curve(self) -> int: @@ -618,6 +627,10 @@ def get_color(self) -> ManimColor: color = property(get_color, set_color) + 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. @@ -2284,6 +2297,9 @@ def __setitem__(self, key: int, value: VMobject | Sequence[VMobject]) -> None: self._assert_valid_submobjects(tuplify(value)) self.submobjects[key] = value + def __getitem__(self, key: int) -> VMobject: + return self.submobjects[key] + class VDict(VMobject, metaclass=ConvertToOpenGL): """A VGroup-like class, also offering submobject access by diff --git a/manim/scene/vector_space_scene.py b/manim/scene/vector_space_scene.py index ccc3780200..47e5d64758 100644 --- a/manim/scene/vector_space_scene.py +++ b/manim/scene/vector_space_scene.py @@ -281,7 +281,9 @@ def get_basis_vector_labels(self, **kwargs: Any) -> VGroup: color (str), label_scale_factor=VECTOR_LABEL_SCALE_FACTOR (int, float), """ - i_hat, j_hat = self.get_basis_vectors() + temp = self.get_basis_vectors() + i_hat = temp.submobjects[0] + j_hat = temp.submobjects[1] return VGroup( *( self.get_vector_label( @@ -296,7 +298,7 @@ def get_basis_vector_labels(self, **kwargs: Any) -> VGroup: def get_vector_label( self, - vector: Vector, + vector: VMobject, label: MathTex | str, at_tip: bool = False, direction: str = "left", @@ -517,7 +519,9 @@ def vector_to_coords( 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() diff --git a/manim/utils/color/core.py b/manim/utils/color/core.py index af25992e59..b251479b47 100644 --- a/manim/utils/color/core.py +++ b/manim/utils/color/core.py @@ -1409,7 +1409,7 @@ def invert_color(color: ManimColorT) -> ManimColorT: def color_gradient( reference_colors: Sequence[ParsableManimColor], length_of_output: int, -) -> list[ManimColor] | ManimColor: +) -> list[ManimColor]: """Create a list of colors interpolated between the input array of colors with a specific number of colors. @@ -1426,7 +1426,7 @@ def color_gradient( A :class:`ManimColor` or a list of interpolated :class:`ManimColor`'s. """ if length_of_output == 0: - return ManimColor(reference_colors[0]) + return [ManimColor(reference_colors[0])] if len(reference_colors) == 1: return [ManimColor(reference_colors[0])] * length_of_output rgbs = [color_to_rgb(color) for color in reference_colors] diff --git a/mypy.ini b/mypy.ini index d0dcd812d6..428493bcdd 100644 --- a/mypy.ini +++ b/mypy.ini @@ -87,9 +87,6 @@ ignore_errors = True [mypy-manim.mobject.logo] ignore_errors = True -[mypy-manim.mobject.mobject] -ignore_errors = True - [mypy-manim.mobject.opengl.opengl_compatibility] ignore_errors = True