From c8a22280df617505871bff4c1cac920eab61541c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 17:11:11 +0000 Subject: [PATCH 01/34] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.9.1 → v0.9.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.1...v0.9.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d92cc048e0..d432c948a2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: check-toml name: Validate Poetry - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.1 + rev: v0.9.2 hooks: - id: ruff name: ruff lint From 8249c13f27c947b8490cc5dd8a0b59eb7f10bdd9 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Fri, 10 Jan 2025 09:13:01 +0100 Subject: [PATCH 02/34] Add type annotations to camera/camera.py --- manim/camera/camera.py | 192 +++++++++++++++------------ manim/mobject/text/tex_mobject.py | 3 + manim/mobject/types/image_mobject.py | 4 +- mypy.ini | 3 + 4 files changed, 118 insertions(+), 84 deletions(-) diff --git a/manim/camera/camera.py b/manim/camera/camera.py index af5899c5c5..79c83e9de3 100644 --- a/manim/camera/camera.py +++ b/manim/camera/camera.py @@ -16,6 +16,9 @@ import numpy as np from PIL import Image from scipy.spatial.distance import pdist +from typing_extensions import Self + +from manim.typing import PixelArray from .. import config, logger from ..constants import * @@ -84,8 +87,8 @@ def __init__( frame_rate: float | None = None, background_color: ParsableManimColor | None = None, background_opacity: float | None = None, - **kwargs, - ): + **kwargs: Any, + ) -> None: self.background_image = background_image self.frame_center = frame_center self.image_mode = image_mode @@ -116,11 +119,13 @@ def __init__( self.frame_rate = frame_rate if background_color is None: - self._background_color = ManimColor.parse(config["background_color"]) + self._background_color: ManimColor = ManimColor.parse( + config["background_color"] + ) else: self._background_color = ManimColor.parse(background_color) if background_opacity is None: - self._background_opacity = config["background_opacity"] + self._background_opacity: float = config["background_opacity"] else: self._background_opacity = background_opacity @@ -129,7 +134,7 @@ def __init__( self.max_allowable_norm = config["frame_width"] self.rgb_max_val = np.iinfo(self.pixel_array_dtype).max - self.pixel_array_to_cairo_context = {} + self.pixel_array_to_cairo_context: dict[int, cairo.Context] = {} # Contains the correct method to process a list of Mobjects of the # corresponding class. If a Mobject is not an instance of a class in @@ -140,7 +145,7 @@ def __init__( self.resize_frame_shape() self.reset() - def __deepcopy__(self, memo): + def __deepcopy__(self, memo: Any) -> Camera: # This is to address a strange bug where deepcopying # will result in a segfault, which is somehow related # to the aggdraw library @@ -148,24 +153,26 @@ def __deepcopy__(self, memo): return copy.copy(self) @property - def background_color(self): + def background_color(self) -> ManimColor: return self._background_color @background_color.setter - def background_color(self, color): + def background_color(self, color: ManimColor) -> None: self._background_color = color self.init_background() @property - def background_opacity(self): + def background_opacity(self) -> float: return self._background_opacity @background_opacity.setter - def background_opacity(self, alpha): + def background_opacity(self, alpha: float) -> None: self._background_opacity = alpha self.init_background() - def type_or_raise(self, mobject: Mobject): + def type_or_raise( + self, mobject: Mobject + ) -> type[VMobject] | type[PMobject] | type[AbstractImageMobject] | type[Mobject]: """Return the type of mobject, if it is a type that can be rendered. If `mobject` is an instance of a class that inherits from a class that @@ -206,7 +213,7 @@ def type_or_raise(self, mobject: Mobject): return _type raise TypeError(f"Displaying an object of class {_type} is not supported") - def reset_pixel_shape(self, new_height: float, new_width: float): + def reset_pixel_shape(self, new_height: float, new_width: float) -> None: """This method resets the height and width of a single pixel to the passed new_height and new_width. @@ -223,7 +230,7 @@ def reset_pixel_shape(self, new_height: float, new_width: float): self.resize_frame_shape() self.reset() - def resize_frame_shape(self, fixed_dimension: int = 0): + def resize_frame_shape(self, fixed_dimension: int = 0) -> None: """ Changes frame_shape to match the aspect ratio of the pixels, where fixed_dimension determines @@ -248,7 +255,7 @@ def resize_frame_shape(self, fixed_dimension: int = 0): self.frame_height = frame_height self.frame_width = frame_width - def init_background(self): + def init_background(self) -> None: """Initialize the background. If self.background_image is the path of an image the image is set as background; else, the default @@ -274,7 +281,9 @@ def init_background(self): ) self.background[:, :] = background_rgba - def get_image(self, pixel_array: np.ndarray | list | tuple | None = None): + def get_image( + self, pixel_array: PixelArray | list | tuple | None = None + ) -> PixelArray: """Returns an image from the passed pixel array, or from the current frame if the passed pixel array is none. @@ -290,12 +299,14 @@ def get_image(self, pixel_array: np.ndarray | list | tuple | None = None): The PIL image of the array. """ if pixel_array is None: - pixel_array = self.pixel_array + # TODO: + # error: Cannot determine type of "pixel_array" [has-type] + pixel_array = self.pixel_array # type: ignore[has-type] return Image.fromarray(pixel_array, mode=self.image_mode) def convert_pixel_array( - self, pixel_array: np.ndarray | list | tuple, convert_from_floats: bool = False - ): + self, pixel_array: PixelArray | list | tuple, convert_from_floats: bool = False + ) -> PixelArray: """Converts a pixel array from values that have floats in then to proper RGB values. @@ -321,8 +332,8 @@ def convert_pixel_array( return retval def set_pixel_array( - self, pixel_array: np.ndarray | list | tuple, convert_from_floats: bool = False - ): + self, pixel_array: PixelArray | list | tuple, convert_from_floats: bool = False + ) -> None: """Sets the pixel array of the camera to the passed pixel array. Parameters @@ -335,7 +346,9 @@ def set_pixel_array( converted_array = self.convert_pixel_array(pixel_array, convert_from_floats) if not ( hasattr(self, "pixel_array") - and self.pixel_array.shape == converted_array.shape + # TODO: + # error: Cannot determine type of "pixel_array" [has-type] + and self.pixel_array.shape == converted_array.shape # type: ignore[has-type] ): self.pixel_array = converted_array else: @@ -343,8 +356,8 @@ def set_pixel_array( self.pixel_array[:, :, :] = converted_array[:, :, :] def set_background( - self, pixel_array: np.ndarray | list | tuple, convert_from_floats: bool = False - ): + self, pixel_array: PixelArray | list | tuple, convert_from_floats: bool = False + ) -> None: """Sets the background to the passed pixel_array after converting to valid RGB values. @@ -360,7 +373,7 @@ def set_background( # TODO, this should live in utils, not as a method of Camera def make_background_from_func( self, coords_to_colors_func: Callable[[np.ndarray], np.ndarray] - ): + ) -> PixelArray: """ Makes a pixel array for the background by using coords_to_colors_func to determine each pixel's color. Each input pixel's color. Each input to coords_to_colors_func is an (x, y) pair in space (in ordinary space coordinates; not @@ -386,7 +399,7 @@ def make_background_from_func( def set_background_from_func( self, coords_to_colors_func: Callable[[np.ndarray], np.ndarray] - ): + ) -> None: """ Sets the background to a pixel array using coords_to_colors_func to determine each pixel's color. Each input pixel's color. Each input to coords_to_colors_func is an (x, y) pair in space (in ordinary space coordinates; not @@ -400,7 +413,7 @@ def set_background_from_func( """ self.set_background(self.make_background_from_func(coords_to_colors_func)) - def reset(self): + def reset(self) -> Self: """Resets the camera's pixel array to that of the background @@ -412,7 +425,7 @@ def reset(self): self.set_pixel_array(self.background) return self - def set_frame_to_background(self, background): + def set_frame_to_background(self, background: PixelArray) -> None: self.set_pixel_array(background) #### @@ -422,7 +435,7 @@ def get_mobjects_to_display( mobjects: Iterable[Mobject], include_submobjects: bool = True, excluded_mobjects: list | None = None, - ): + ) -> list[Mobject]: """Used to get the list of mobjects to display with the camera. @@ -454,7 +467,7 @@ def get_mobjects_to_display( mobjects = list_difference_update(mobjects, all_excluded) return list(mobjects) - def is_in_frame(self, mobject: Mobject): + def is_in_frame(self, mobject: Mobject) -> bool: """Checks whether the passed mobject is in frame or not. @@ -481,7 +494,7 @@ def is_in_frame(self, mobject: Mobject): ], ) - def capture_mobject(self, mobject: Mobject, **kwargs: Any): + def capture_mobject(self, mobject: Mobject, **kwargs: Any) -> None: """Capture mobjects by storing it in :attr:`pixel_array`. This is a single-mobject version of :meth:`capture_mobjects`. @@ -497,7 +510,7 @@ def capture_mobject(self, mobject: Mobject, **kwargs: Any): """ return self.capture_mobjects([mobject], **kwargs) - def capture_mobjects(self, mobjects: Iterable[Mobject], **kwargs): + def capture_mobjects(self, mobjects: Iterable[Mobject], **kwargs: Any) -> None: """Capture mobjects by printing them on :attr:`pixel_array`. This is the essential function that converts the contents of a Scene @@ -525,14 +538,16 @@ def capture_mobjects(self, mobjects: Iterable[Mobject], **kwargs): # partition while at the same time preserving order. mobjects = self.get_mobjects_to_display(mobjects, **kwargs) for group_type, group in it.groupby(mobjects, self.type_or_raise): - self.display_funcs[group_type](list(group), self.pixel_array) + # TODO + # error: Call to untyped function (unknown) in typed context [no-untyped-call] + self.display_funcs[group_type](list(group), self.pixel_array) # type: ignore[no-untyped-call] # Methods associated with svg rendering # NOTE: None of the methods below have been mentioned outside of their definitions. Their DocStrings are not as # detailed as possible. - def get_cached_cairo_context(self, pixel_array: np.ndarray): + def get_cached_cairo_context(self, pixel_array: PixelArray) -> cairo.Context: """Returns the cached cairo context of the passed pixel array if it exists, and None if it doesn't. @@ -548,7 +563,7 @@ def get_cached_cairo_context(self, pixel_array: np.ndarray): """ return self.pixel_array_to_cairo_context.get(id(pixel_array), None) - def cache_cairo_context(self, pixel_array: np.ndarray, ctx: cairo.Context): + def cache_cairo_context(self, pixel_array: PixelArray, ctx: cairo.Context) -> None: """Caches the passed Pixel array into a Cairo Context Parameters @@ -560,7 +575,7 @@ def cache_cairo_context(self, pixel_array: np.ndarray, ctx: cairo.Context): """ self.pixel_array_to_cairo_context[id(pixel_array)] = ctx - def get_cairo_context(self, pixel_array: np.ndarray): + def get_cairo_context(self, pixel_array: PixelArray) -> cairo.Context: """Returns the cairo context for a pixel array after caching it to self.pixel_array_to_cairo_context If that array has already been cached, it returns the @@ -606,8 +621,8 @@ def get_cairo_context(self, pixel_array: np.ndarray): return ctx def display_multiple_vectorized_mobjects( - self, vmobjects: list, pixel_array: np.ndarray - ): + self, vmobjects: list[VMobject], pixel_array: PixelArray + ) -> None: """Displays multiple VMobjects in the pixel_array Parameters @@ -630,8 +645,8 @@ def display_multiple_vectorized_mobjects( ) def display_multiple_non_background_colored_vmobjects( - self, vmobjects: list, pixel_array: np.ndarray - ): + self, vmobjects: Iterable[VMobject], pixel_array: PixelArray + ) -> None: """Displays multiple VMobjects in the cairo context, as long as they don't have background colors. @@ -646,7 +661,7 @@ def display_multiple_non_background_colored_vmobjects( for vmobject in vmobjects: self.display_vectorized(vmobject, ctx) - def display_vectorized(self, vmobject: VMobject, ctx: cairo.Context): + def display_vectorized(self, vmobject: VMobject, ctx: cairo.Context) -> Self: """Displays a VMobject in the cairo context Parameters @@ -667,7 +682,7 @@ def display_vectorized(self, vmobject: VMobject, ctx: cairo.Context): self.apply_stroke(ctx, vmobject) return self - def set_cairo_context_path(self, ctx: cairo.Context, vmobject: VMobject): + def set_cairo_context_path(self, ctx: cairo.Context, vmobject: VMobject) -> Self: """Sets a path for the cairo context with the vmobject passed Parameters @@ -686,7 +701,9 @@ def set_cairo_context_path(self, ctx: cairo.Context, vmobject: VMobject): # TODO, shouldn't this be handled in transform_points_pre_display? # points = points - self.get_frame_center() if len(points) == 0: - return + # TODO: + # Here the return value is modified. Is that ok? + return self ctx.new_path() subpaths = vmobject.gen_subpaths_from_points_2d(points) @@ -702,8 +719,8 @@ def set_cairo_context_path(self, ctx: cairo.Context, vmobject: VMobject): return self def set_cairo_context_color( - self, ctx: cairo.Context, rgbas: np.ndarray, vmobject: VMobject - ): + self, ctx: cairo.Context, rgbas: PixelArray, vmobject: VMobject + ) -> Self: """Sets the color of the cairo context Parameters @@ -735,7 +752,7 @@ def set_cairo_context_color( ctx.set_source(pat) return self - def apply_fill(self, ctx: cairo.Context, vmobject: VMobject): + def apply_fill(self, ctx: cairo.Context, vmobject: VMobject) -> Self: """Fills the cairo context Parameters @@ -756,7 +773,7 @@ def apply_fill(self, ctx: cairo.Context, vmobject: VMobject): def apply_stroke( self, ctx: cairo.Context, vmobject: VMobject, background: bool = False - ): + ) -> Self: """Applies a stroke to the VMobject in the cairo context. Parameters @@ -795,7 +812,9 @@ def apply_stroke( ctx.stroke_preserve() return self - def get_stroke_rgbas(self, vmobject: VMobject, background: bool = False): + def get_stroke_rgbas( + self, vmobject: VMobject, background: bool = False + ) -> PixelArray: """Gets the RGBA array for the stroke of the passed VMobject. @@ -814,7 +833,7 @@ def get_stroke_rgbas(self, vmobject: VMobject, background: bool = False): """ return vmobject.get_stroke_rgbas(background) - def get_fill_rgbas(self, vmobject: VMobject): + def get_fill_rgbas(self, vmobject: VMobject) -> PixelArray: """Returns the RGBA array of the fill of the passed VMobject Parameters @@ -829,13 +848,15 @@ def get_fill_rgbas(self, vmobject: VMobject): """ return vmobject.get_fill_rgbas() - def get_background_colored_vmobject_displayer(self): + def get_background_colored_vmobject_displayer( + self, + ) -> BackgroundColoredVMobjectDisplayer: """Returns the background_colored_vmobject_displayer if it exists or makes one and returns it if not. Returns ------- - BackGroundColoredVMobjectDisplayer + BackgroundColoredVMobjectDisplayer Object that displays VMobjects that have the same color as the background. """ @@ -843,11 +864,11 @@ def get_background_colored_vmobject_displayer(self): bcvd = "background_colored_vmobject_displayer" if not hasattr(self, bcvd): setattr(self, bcvd, BackgroundColoredVMobjectDisplayer(self)) - return getattr(self, bcvd) + return getattr(self, bcvd) # type: ignore[no-any-return] def display_multiple_background_colored_vmobjects( - self, cvmobjects: list, pixel_array: np.ndarray - ): + self, cvmobjects: Iterable[VMobject], pixel_array: PixelArray + ) -> Self: """Displays multiple vmobjects that have the same color as the background. Parameters @@ -873,8 +894,8 @@ def display_multiple_background_colored_vmobjects( # As a result, the other methods do not have as detailed docstrings as would be preferred. def display_multiple_point_cloud_mobjects( - self, pmobjects: list, pixel_array: np.ndarray - ): + self, pmobjects: list, pixel_array: PixelArray + ) -> None: """Displays multiple PMobjects by modifying the passed pixel array. Parameters @@ -899,8 +920,8 @@ def display_point_cloud( points: list, rgbas: np.ndarray, thickness: float, - pixel_array: np.ndarray, - ): + pixel_array: PixelArray, + ) -> None: """Displays a PMobject by modifying the pixel array suitably. TODO: Write a description for the rgbas argument. @@ -948,7 +969,7 @@ def display_point_cloud( def display_multiple_image_mobjects( self, image_mobjects: list, pixel_array: np.ndarray - ): + ) -> None: """Displays multiple image mobjects by modifying the passed pixel_array. Parameters @@ -963,7 +984,7 @@ def display_multiple_image_mobjects( def display_image_mobject( self, image_mobject: AbstractImageMobject, pixel_array: np.ndarray - ): + ) -> None: """Displays an ImageMobject by changing the pixel_array suitably. Parameters @@ -1020,7 +1041,9 @@ def display_image_mobject( # Paint on top of existing pixel array self.overlay_PIL_image(pixel_array, full_image) - def overlay_rgba_array(self, pixel_array: np.ndarray, new_array: np.ndarray): + def overlay_rgba_array( + self, pixel_array: np.ndarray, new_array: np.ndarray + ) -> None: """Overlays an RGBA array on top of the given Pixel array. Parameters @@ -1032,7 +1055,7 @@ def overlay_rgba_array(self, pixel_array: np.ndarray, new_array: np.ndarray): """ self.overlay_PIL_image(pixel_array, self.get_image(new_array)) - def overlay_PIL_image(self, pixel_array: np.ndarray, image: Image): + def overlay_PIL_image(self, pixel_array: np.ndarray, image: Image) -> None: """Overlays a PIL image on the passed pixel array. Parameters @@ -1047,7 +1070,7 @@ def overlay_PIL_image(self, pixel_array: np.ndarray, image: Image): dtype="uint8", ) - def adjust_out_of_range_points(self, points: np.ndarray): + def adjust_out_of_range_points(self, points: np.ndarray) -> np.ndarray: """If any of the points in the passed array are out of the viable range, they are adjusted suitably. @@ -1078,9 +1101,10 @@ def adjust_out_of_range_points(self, points: np.ndarray): def transform_points_pre_display( self, - mobject, - points, - ): # TODO: Write more detailed docstrings for this method. + mobject: Mobject, + points: np.ndarray, + ) -> np.ndarray: + # TODO: Write more detailed docstrings for this method. # NOTE: There seems to be an unused argument `mobject`. # Subclasses (like ThreeDCamera) may want to @@ -1093,9 +1117,9 @@ def transform_points_pre_display( def points_to_pixel_coords( self, - mobject, - points, - ): # TODO: Write more detailed docstrings for this method. + mobject: Mobject, + points: np.ndarray, + ) -> np.ndarray: # TODO: Write more detailed docstrings for this method. points = self.transform_points_pre_display(mobject, points) shifted_points = points - self.frame_center @@ -1115,7 +1139,7 @@ def points_to_pixel_coords( result[:, 1] = shifted_points[:, 1] * height_mult + height_add return result.astype("int") - def on_screen_pixels(self, pixel_coords: np.ndarray): + def on_screen_pixels(self, pixel_coords: np.ndarray) -> PixelArray: """Returns array of pixels that are on the screen from a given array of pixel_coordinates @@ -1154,12 +1178,12 @@ def adjusted_thickness(self, thickness: float) -> float: the camera. """ # TODO: This seems...unsystematic - big_sum = op.add(config["pixel_height"], config["pixel_width"]) - this_sum = op.add(self.pixel_height, self.pixel_width) + big_sum: float = op.add(config["pixel_height"], config["pixel_width"]) + this_sum: float = op.add(self.pixel_height, self.pixel_width) factor = big_sum / this_sum return 1 + (thickness - 1) * factor - def get_thickening_nudges(self, thickness: float): + def get_thickening_nudges(self, thickness: float) -> PixelArray: """Determine a list of vectors used to nudge two-dimensional pixel coordinates. @@ -1176,7 +1200,9 @@ def get_thickening_nudges(self, thickness: float): _range = list(range(-thickness // 2 + 1, thickness // 2 + 1)) return np.array(list(it.product(_range, _range))) - def thickened_coordinates(self, pixel_coords: np.ndarray, thickness: float): + def thickened_coordinates( + self, pixel_coords: np.ndarray, thickness: float + ) -> PixelArray: """Returns thickened coordinates for a passed array of pixel coords and a thickness to thicken by. @@ -1198,7 +1224,7 @@ def thickened_coordinates(self, pixel_coords: np.ndarray, thickness: float): return pixel_coords.reshape((size // 2, 2)) # TODO, reimplement using cairo matrix - def get_coords_of_all_pixels(self): + def get_coords_of_all_pixels(self) -> PixelArray: """Returns the cartesian coordinates of each pixel. Returns @@ -1246,20 +1272,20 @@ class BackgroundColoredVMobjectDisplayer: def __init__(self, camera: Camera): self.camera = camera - self.file_name_to_pixel_array_map = {} + self.file_name_to_pixel_array_map: dict[str, PixelArray] = {} self.pixel_array = np.array(camera.pixel_array) self.reset_pixel_array() - def reset_pixel_array(self): + def reset_pixel_array(self) -> None: self.pixel_array[:, :] = 0 def resize_background_array( self, - background_array: np.ndarray, + background_array: PixelArray, new_width: float, new_height: float, mode: str = "RGBA", - ): + ) -> PixelArray: """Resizes the pixel array representing the background. Parameters @@ -1284,8 +1310,8 @@ def resize_background_array( return np.array(resized_image) def resize_background_array_to_match( - self, background_array: np.ndarray, pixel_array: np.ndarray - ): + self, background_array: PixelArray, pixel_array: PixelArray + ) -> PixelArray: """Resizes the background array to match the passed pixel array. Parameters @@ -1304,7 +1330,9 @@ def resize_background_array_to_match( mode = "RGBA" if pixel_array.shape[2] == 4 else "RGB" return self.resize_background_array(background_array, width, height, mode) - def get_background_array(self, image: Image.Image | pathlib.Path | str): + def get_background_array( + self, image: Image.Image | pathlib.Path | str + ) -> PixelArray: """Gets the background array that has the passed file_name. Parameters @@ -1333,7 +1361,7 @@ def get_background_array(self, image: Image.Image | pathlib.Path | str): self.file_name_to_pixel_array_map[image_key] = back_array return back_array - def display(self, *cvmobjects: VMobject): + def display(self, *cvmobjects: VMobject) -> PixelArray | None: """Displays the colored VMobjects. Parameters diff --git a/manim/mobject/text/tex_mobject.py b/manim/mobject/text/tex_mobject.py index 26334a60d9..c3a03e5746 100644 --- a/manim/mobject/text/tex_mobject.py +++ b/manim/mobject/text/tex_mobject.py @@ -1,3 +1,6 @@ +# The following line is needed to avoid some strange +# mypy errors related to the code around line 366. +# mypy: disable_error_code = has-type r"""Mobjects representing text rendered using LaTeX. .. important:: diff --git a/manim/mobject/types/image_mobject.py b/manim/mobject/types/image_mobject.py index 56029f941e..5f83dd0240 100644 --- a/manim/mobject/types/image_mobject.py +++ b/manim/mobject/types/image_mobject.py @@ -28,7 +28,7 @@ import numpy.typing as npt from typing_extensions import Self - from manim.typing import StrPath + from manim.typing import PixelArray, StrPath class AbstractImageMobject(Mobject): @@ -57,7 +57,7 @@ def __init__( self.set_resampling_algorithm(resampling_algorithm) super().__init__(**kwargs) - def get_pixel_array(self) -> None: + def get_pixel_array(self) -> PixelArray: raise NotImplementedError() def set_color(self, color, alpha=None, family=True): diff --git a/mypy.ini b/mypy.ini index 80571869be..1647b4f4ef 100644 --- a/mypy.ini +++ b/mypy.ini @@ -58,6 +58,9 @@ ignore_errors = True [mypy-manim.camera.*] ignore_errors = True +[mypy-manim.camera.camera.*] +ignore_errors = False + [mypy-manim.cli.*] ignore_errors = False From bddb6ad2424857aaf68aa4844bf1239923cbeca2 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Mon, 20 Jan 2025 22:45:37 +0100 Subject: [PATCH 03/34] Part 2 --- manim/camera/moving_camera.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/manim/camera/moving_camera.py b/manim/camera/moving_camera.py index 1d01d01e22..ba89ee2c22 100644 --- a/manim/camera/moving_camera.py +++ b/manim/camera/moving_camera.py @@ -10,6 +10,9 @@ __all__ = ["MovingCamera"] +from collections.abc import Iterable +from typing import Any + import numpy as np from .. import config @@ -17,7 +20,7 @@ from ..constants import DOWN, LEFT, RIGHT, UP from ..mobject.frame import ScreenRectangle from ..mobject.mobject import Mobject -from ..utils.color import WHITE +from ..utils.color import WHITE, ManimColor class MovingCamera(Camera): @@ -33,11 +36,11 @@ class MovingCamera(Camera): def __init__( self, frame=None, - fixed_dimension=0, # width - default_frame_stroke_color=WHITE, - default_frame_stroke_width=0, - **kwargs, - ): + fixed_dimension: int = 0, # width + default_frame_stroke_color: ManimColor = WHITE, + default_frame_stroke_width: int = 0, + **kwargs: Any, + ) -> None: """ Frame is a Mobject, (should almost certainly be a rectangle) determining which region of space the camera displays @@ -123,7 +126,7 @@ def frame_center(self, frame_center: np.ndarray | list | tuple | Mobject): """ self.frame.move_to(frame_center) - def capture_mobjects(self, mobjects, **kwargs): + def capture_mobjects(self, mobjects: Iterable[Mobject], **kwargs: Any) -> None: # self.reset_frame_center() # self.realign_frame_shape() super().capture_mobjects(mobjects, **kwargs) From ea60db4e21ec640819f50d730241c3d7e2d3cf01 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Mon, 20 Jan 2025 22:45:49 +0100 Subject: [PATCH 04/34] Part 3 --- manim/camera/camera.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/manim/camera/camera.py b/manim/camera/camera.py index 79c83e9de3..700c164861 100644 --- a/manim/camera/camera.py +++ b/manim/camera/camera.py @@ -299,9 +299,7 @@ def get_image( The PIL image of the array. """ if pixel_array is None: - # TODO: - # error: Cannot determine type of "pixel_array" [has-type] - pixel_array = self.pixel_array # type: ignore[has-type] + pixel_array = self.pixel_array return Image.fromarray(pixel_array, mode=self.image_mode) def convert_pixel_array( @@ -343,14 +341,14 @@ def set_pixel_array( convert_from_floats Whether or not to convert float values to proper RGB values, by default False """ - converted_array = self.convert_pixel_array(pixel_array, convert_from_floats) + converted_array: PixelArray = self.convert_pixel_array( + pixel_array, convert_from_floats + ) if not ( hasattr(self, "pixel_array") - # TODO: - # error: Cannot determine type of "pixel_array" [has-type] - and self.pixel_array.shape == converted_array.shape # type: ignore[has-type] + and self.pixel_array.shape == converted_array.shape ): - self.pixel_array = converted_array + self.pixel_array: PixelArray = converted_array else: # Set in place self.pixel_array[:, :, :] = converted_array[:, :, :] From d9dcb3d92cc9760c298b6108c3bf9068ac112980 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Fri, 17 Jan 2025 21:19:11 +0100 Subject: [PATCH 05/34] Part 1 --- manim/mobject/types/image_mobject.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/manim/mobject/types/image_mobject.py b/manim/mobject/types/image_mobject.py index 5f83dd0240..c0659c672b 100644 --- a/manim/mobject/types/image_mobject.py +++ b/manim/mobject/types/image_mobject.py @@ -14,6 +14,7 @@ from manim.mobject.geometry.shape_matchers import SurroundingRectangle from ... import config +from ...camera.moving_camera import MovingCamera from ...constants import * from ...mobject.mobject import Mobject from ...utils.bezier import interpolate @@ -303,7 +304,7 @@ def get_style(self) -> dict[str, Any]: class ImageMobjectFromCamera(AbstractImageMobject): def __init__( self, - camera, + camera: MovingCamera, default_display_frame_config: dict[str, Any] | None = None, **kwargs: Any, ) -> None: From 97cc36ae3e555bb48ad7c978d064254c98587983 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Fri, 17 Jan 2025 21:30:49 +0100 Subject: [PATCH 06/34] Add type annotations to mobject/graphing/scale.py --- manim/mobject/graphing/scale.py | 18 +++++++++++++----- mypy.ini | 3 +++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/manim/mobject/graphing/scale.py b/manim/mobject/graphing/scale.py index 78ffa2308b..1a6635727f 100644 --- a/manim/mobject/graphing/scale.py +++ b/manim/mobject/graphing/scale.py @@ -11,6 +11,8 @@ from manim.mobject.text.numbers import Integer if TYPE_CHECKING: + from typing import Callable + from manim.mobject.mobject import Mobject @@ -139,15 +141,19 @@ def __init__(self, base: float = 10, custom_labels: bool = True): def function(self, value: float) -> float: """Scales the value to fit it to a logarithmic scale.``self.function(5)==10**5``""" - return self.base**value + val: float = self.base**value + return val def inverse_function(self, value: float) -> float: """Inverse of ``function``. The value must be greater than 0""" if isinstance(value, np.ndarray): condition = value.any() <= 0 - def func(value, base): - return np.log(value) / np.log(base) + func: Callable[[float, float], float] + + def func(value: float, base: float) -> float: + val: float = np.log(value) / np.log(base) + return val else: condition = value <= 0 func = math.log @@ -177,11 +183,13 @@ def get_custom_labels( Additional arguments to be passed to :class:`~.Integer`. """ # uses `format` syntax to control the number of decimal places. - tex_labels = [ + tex_labels: list[Mobject] = [ Integer( self.base, unit="^{%s}" % (f"{self.inverse_function(i):.{unit_decimal_places}f}"), # noqa: UP031 - **base_config, + # TODO: + # error: Argument 3 to "Integer" has incompatible type "**dict[str, dict[str, Any]]"; expected "int" [arg-type] + **base_config, # type: ignore[arg-type] ) for i in val_range ] diff --git a/mypy.ini b/mypy.ini index 1647b4f4ef..826626ad9d 100644 --- a/mypy.ini +++ b/mypy.ini @@ -67,6 +67,9 @@ ignore_errors = False [mypy-manim.cli.cfg.*] ignore_errors = False +[mypy-manim.mobject.graphing.scale.*] +ignore_errors = False + [mypy-manim.gui.*] ignore_errors = True From 65129cc1b7f68cf953e29b13ca566709c642c61e Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Fri, 17 Jan 2025 22:26:48 +0100 Subject: [PATCH 07/34] Add type annotations to most of mobject/graphing/probability.py --- manim/mobject/graphing/number_line.py | 2 +- manim/mobject/graphing/probability.py | 141 +++++++++++++++++--------- mypy.ini | 3 + 3 files changed, 97 insertions(+), 49 deletions(-) diff --git a/manim/mobject/graphing/number_line.py b/manim/mobject/graphing/number_line.py index 017fac5bcb..af834cda37 100644 --- a/manim/mobject/graphing/number_line.py +++ b/manim/mobject/graphing/number_line.py @@ -158,7 +158,7 @@ def __init__( include_numbers: bool = False, font_size: float = 36, label_direction: Sequence[float] = DOWN, - label_constructor: VMobject = MathTex, + label_constructor: type[MathTex] = MathTex, scaling: _ScaleBase = LinearBase(), line_to_number_buff: float = MED_SMALL_BUFF, decimal_number_config: dict | None = None, diff --git a/manim/mobject/graphing/probability.py b/manim/mobject/graphing/probability.py index 24134c0a7a..f8adc83c19 100644 --- a/manim/mobject/graphing/probability.py +++ b/manim/mobject/graphing/probability.py @@ -6,6 +6,7 @@ from collections.abc import Iterable, MutableSequence, Sequence +from typing import Any import numpy as np @@ -17,7 +18,8 @@ from manim.mobject.opengl.opengl_mobject import OpenGLMobject from manim.mobject.svg.brace import Brace from manim.mobject.text.tex_mobject import MathTex, Tex -from manim.mobject.types.vectorized_mobject import VGroup, VMobject +from manim.mobject.types.vectorized_mobject import VGroup +from manim.typing import Vector3D from manim.utils.color import ( BLUE_E, DARK_GREY, @@ -54,14 +56,14 @@ def construct(self): def __init__( self, - height=3, - width=3, - fill_color=DARK_GREY, - fill_opacity=1, - stroke_width=0.5, - stroke_color=LIGHT_GREY, - default_label_scale_val=1, - ): + height: float = 3, + width: float = 3, + fill_color: ParsableManimColor = DARK_GREY, + fill_opacity: float = 1, + stroke_width: float = 0.5, + stroke_color: ParsableManimColor = LIGHT_GREY, + default_label_scale_val: float = 1, + ) -> None: super().__init__( height=height, width=width, @@ -72,7 +74,9 @@ def __init__( ) self.default_label_scale_val = default_label_scale_val - def add_title(self, title="Sample space", buff=MED_SMALL_BUFF): + def add_title( + self, title: str = "Sample space", buff: float = MED_SMALL_BUFF + ) -> None: # TODO, should this really exist in SampleSpaceScene title_mob = Tex(title) if title_mob.width > self.width: @@ -81,23 +85,30 @@ def add_title(self, title="Sample space", buff=MED_SMALL_BUFF): self.title = title_mob self.add(title_mob) - def add_label(self, label): + def add_label(self, label: str) -> None: self.label = label - def complete_p_list(self, p_list): + def complete_p_list(self, p_list: list[tuple]) -> list[float]: new_p_list = list(tuplify(p_list)) remainder = 1.0 - sum(new_p_list) if abs(remainder) > EPSILON: new_p_list.append(remainder) return new_p_list - def get_division_along_dimension(self, p_list, dim, colors, vect): - p_list = self.complete_p_list(p_list) - colors = color_gradient(colors, len(p_list)) + def get_division_along_dimension( + self, + p_list: list[tuple], + dim: int, + colors: Sequence[ParsableManimColor], + vect: Vector3D, + ) -> VGroup: + p_list_complete = self.complete_p_list(p_list) + colors_in_gradient = color_gradient(colors, len(p_list)) + assert isinstance(colors_in_gradient, list) last_point = self.get_edge_center(-vect) parts = VGroup() - for factor, color in zip(p_list, colors): + for factor, color in zip(p_list_complete, colors_in_gradient): part = SampleSpace() part.set_fill(color, 1) part.replace(self, stretch=True) @@ -107,28 +118,42 @@ def get_division_along_dimension(self, p_list, dim, colors, vect): parts.add(part) return parts - def get_horizontal_division(self, p_list, colors=[GREEN_E, BLUE_E], vect=DOWN): + def get_horizontal_division( + self, + p_list: list[tuple], + colors: Sequence[ParsableManimColor] = [GREEN_E, BLUE_E], + vect: Vector3D = DOWN, + ) -> VGroup: return self.get_division_along_dimension(p_list, 1, colors, vect) - def get_vertical_division(self, p_list, colors=[MAROON_B, YELLOW], vect=RIGHT): + def get_vertical_division( + self, + p_list: list[tuple], + colors: Sequence[ParsableManimColor] = [MAROON_B, YELLOW], + vect: Vector3D = RIGHT, + ) -> VGroup: return self.get_division_along_dimension(p_list, 0, colors, vect) - def divide_horizontally(self, *args, **kwargs): + # TODO: + # error: Function is missing a type annotation for one or more arguments [no-untyped-def] + def divide_horizontally(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def] self.horizontal_parts = self.get_horizontal_division(*args, **kwargs) self.add(self.horizontal_parts) - def divide_vertically(self, *args, **kwargs): + # TODO: + # error: Function is missing a type annotation for one or more arguments [no-untyped-def] + def divide_vertically(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def] self.vertical_parts = self.get_vertical_division(*args, **kwargs) self.add(self.vertical_parts) def get_subdivision_braces_and_labels( self, - parts, - labels, - direction, - buff=SMALL_BUFF, - min_num_quads=1, - ): + parts: VGroup, + labels: list[str | Mobject | OpenGLMobject], + direction: Vector3D, + buff: float = SMALL_BUFF, + min_num_quads: int = 1, + ) -> VGroup: label_mobs = VGroup() braces = VGroup() for label, part in zip(labels, parts): @@ -141,34 +166,47 @@ def get_subdivision_braces_and_labels( label_mob.next_to(brace, direction, buff) braces.add(brace) - label_mobs.add(label_mob) - parts.braces = braces - parts.labels = label_mobs - parts.label_kwargs = { + # TODO: + # error: Argument 1 to "add" of "VGroup" has incompatible type "Mobject | OpenGLMobject"; expected "VMobject | Iterable[VMobject]" [arg-type] + label_mobs.add(label_mob) # type: ignore[arg-type] + # TODO: + # error: "VGroup" has no attribute "braces" [attr-defined] + parts.braces = braces # type: ignore[attr-defined] + parts.labels = label_mobs # type: ignore[attr-defined] + parts.label_kwargs = { # type: ignore[attr-defined] "labels": label_mobs.copy(), "direction": direction, "buff": buff, } return VGroup(parts.braces, parts.labels) - def get_side_braces_and_labels(self, labels, direction=LEFT, **kwargs): + def get_side_braces_and_labels( + self, + labels: list[str | Mobject | OpenGLMobject], + direction: Vector3D = LEFT, + **kwargs: Any, + ) -> VGroup: assert hasattr(self, "horizontal_parts") parts = self.horizontal_parts return self.get_subdivision_braces_and_labels( parts, labels, direction, **kwargs ) - def get_top_braces_and_labels(self, labels, **kwargs): + def get_top_braces_and_labels( + self, labels: list[str | Mobject | OpenGLMobject], **kwargs: Any + ) -> VGroup: assert hasattr(self, "vertical_parts") parts = self.vertical_parts return self.get_subdivision_braces_and_labels(parts, labels, UP, **kwargs) - def get_bottom_braces_and_labels(self, labels, **kwargs): + def get_bottom_braces_and_labels( + self, labels: list[str | Mobject | OpenGLMobject], **kwargs: Any + ) -> VGroup: assert hasattr(self, "vertical_parts") parts = self.vertical_parts return self.get_subdivision_braces_and_labels(parts, labels, DOWN, **kwargs) - def add_braces_and_labels(self): + def add_braces_and_labels(self) -> None: for attr in "horizontal_parts", "vertical_parts": if not hasattr(self, attr): continue @@ -177,12 +215,16 @@ def add_braces_and_labels(self): if hasattr(parts, subattr): self.add(getattr(parts, subattr)) - def __getitem__(self, index): + def __getitem__(self, index: int) -> VGroup: if hasattr(self, "horizontal_parts"): - return self.horizontal_parts[index] + val: VGroup = self.horizontal_parts[index] + return val elif hasattr(self, "vertical_parts"): - return self.vertical_parts[index] - return self.split()[index] + val = self.vertical_parts[index] + return val + # TODO: + # error: Incompatible return value type (got "SampleSpace", expected "VGroup") [return-value] + return self.split()[index] # type: ignore[return-value] class BarChart(Axes): @@ -253,8 +295,8 @@ def __init__( bar_width: float = 0.6, bar_fill_opacity: float = 0.7, bar_stroke_width: float = 3, - **kwargs, - ): + **kwargs: Any, + ) -> None: if isinstance(bar_colors, str): logger.warning( "Passing a string to `bar_colors` has been deprecated since v0.15.2 and will be removed after v0.17.0, the parameter must be a list. " @@ -311,7 +353,7 @@ def __init__( self.y_axis.add_numbers() - def _update_colors(self): + def _update_colors(self) -> None: """Initialize the colors of the bars of the chart. Sets the color of ``self.bars`` via ``self.bar_colors``. @@ -321,13 +363,14 @@ def _update_colors(self): """ self.bars.set_color_by_gradient(*self.bar_colors) - def _add_x_axis_labels(self): + def _add_x_axis_labels(self) -> None: """Essentially :meth`:~.NumberLine.add_labels`, but differs in that the direction of the label with respect to the x_axis changes to UP or DOWN depending on the value. UP for negative values and DOWN for positive values. """ + assert isinstance(self.bar_names, list) val_range = np.arange( 0.5, len(self.bar_names), 1 ) # 0.5 shifted so that labels are centered, not on ticks @@ -338,7 +381,7 @@ def _add_x_axis_labels(self): # to accommodate negative bars, the label may need to be # below or above the x_axis depending on the value of the bar direction = UP if self.values[i] < 0 else DOWN - bar_name_label = self.x_axis.label_constructor(bar_name) + bar_name_label: MathTex = self.x_axis.label_constructor(bar_name) bar_name_label.font_size = self.x_axis.font_size bar_name_label.next_to( @@ -398,8 +441,8 @@ def get_bar_labels( color: ParsableManimColor | None = None, font_size: float = 24, buff: float = MED_SMALL_BUFF, - label_constructor: type[VMobject] = Tex, - ): + label_constructor: type[MathTex] = Tex, + ) -> VGroup: """Annotates each bar with its corresponding value. Use ``self.bar_labels`` to access the labels after creation. @@ -431,7 +474,7 @@ def construct(self): """ bar_labels = VGroup() for bar, value in zip(self.bars, self.values): - bar_lbl = label_constructor(str(value)) + bar_lbl: MathTex = label_constructor(str(value)) if color is None: bar_lbl.set_color(bar.get_fill_color()) @@ -446,7 +489,9 @@ def construct(self): return bar_labels - def change_bar_values(self, values: Iterable[float], update_colors: bool = True): + def change_bar_values( + self, values: Iterable[float], update_colors: bool = True + ) -> None: """Updates the height of the bars of the chart. Parameters @@ -512,4 +557,4 @@ def construct(self): if update_colors: self._update_colors() - self.values[: len(values)] = values + self.values[: len(list(values))] = values diff --git a/mypy.ini b/mypy.ini index 826626ad9d..ad355f707c 100644 --- a/mypy.ini +++ b/mypy.ini @@ -70,6 +70,9 @@ ignore_errors = False [mypy-manim.mobject.graphing.scale.*] ignore_errors = False +[mypy-manim.mobject.graphing.probability.*] +ignore_errors = False + [mypy-manim.gui.*] ignore_errors = True From 785ad1ad8670ef42ab20b3a36ec5744e9e32b29e Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Fri, 17 Jan 2025 22:59:57 +0100 Subject: [PATCH 08/34] Add type annotations to mobject/graphing/number_line.py --- manim/mobject/graphing/number_line.py | 95 +++++++++++++++++---------- manim/mobject/graphing/scale.py | 9 ++- mypy.ini | 3 + 3 files changed, 70 insertions(+), 37 deletions(-) diff --git a/manim/mobject/graphing/number_line.py b/manim/mobject/graphing/number_line.py index af834cda37..f12877521e 100644 --- a/manim/mobject/graphing/number_line.py +++ b/manim/mobject/graphing/number_line.py @@ -12,6 +12,10 @@ from typing import TYPE_CHECKING, Callable if TYPE_CHECKING: + from typing import Any + + from typing_extensions import Self + from manim.mobject.geometry.tips import ArrowTip from manim.typing import Point3DLike @@ -164,8 +168,8 @@ def __init__( decimal_number_config: dict | None = None, numbers_to_exclude: Iterable[float] | None = None, numbers_to_include: Iterable[float] | None = None, - **kwargs, - ): + **kwargs: Any, + ) -> None: # avoid mutable arguments in defaults if numbers_to_exclude is None: numbers_to_exclude = [] @@ -189,6 +193,9 @@ def __init__( # turn into a NumPy array to scale by just applying the function self.x_range = np.array(x_range, dtype=float) + self.x_min: float + self.x_max: float + self.x_step: float self.x_min, self.x_max, self.x_step = scaling.function(self.x_range) self.length = length self.unit_size = unit_size @@ -250,7 +257,9 @@ def __init__( dict( zip( tick_range, - self.scaling.get_custom_labels( + # TODO: + # Argument 2 to "zip" has incompatible type "Iterable[Mobject]"; expected "Iterable[str | float | VMobject]" [arg-type] + self.scaling.get_custom_labels( # type: ignore[arg-type] tick_range, unit_decimal_places=decimal_number_config[ "num_decimal_places" @@ -267,21 +276,25 @@ def __init__( font_size=self.font_size, ) - def rotate_about_zero(self, angle: float, axis: Sequence[float] = OUT, **kwargs): + def rotate_about_zero( + self, angle: float, axis: Sequence[float] = OUT, **kwargs: Any + ) -> Self: return self.rotate_about_number(0, angle, axis, **kwargs) def rotate_about_number( - self, number: float, angle: float, axis: Sequence[float] = OUT, **kwargs - ): + self, number: float, angle: float, axis: Sequence[float] = OUT, **kwargs: Any + ) -> Self: return self.rotate(angle, axis, about_point=self.n2p(number), **kwargs) - def add_ticks(self): + def add_ticks(self) -> None: """Adds ticks to the number line. Ticks can be accessed after creation via ``self.ticks``. """ ticks = VGroup() elongated_tick_size = self.tick_size * self.longer_tick_multiple - elongated_tick_offsets = self.numbers_with_elongated_ticks - self.x_min + elongated_tick_offsets = ( + np.array(self.numbers_with_elongated_ticks) - self.x_min + ) for x in self.get_tick_range(): size = self.tick_size if np.any(np.isclose(x - self.x_min, elongated_tick_offsets)): @@ -413,19 +426,22 @@ def point_to_number(self, point: Sequence[float]) -> float: point = np.asarray(point) start, end = self.get_start_and_end() unit_vect = normalize(end - start) - proportion = np.dot(point - start, unit_vect) / np.dot(end - start, unit_vect) + proportion: float = np.dot(point - start, unit_vect) / np.dot( + end - start, unit_vect + ) return interpolate(self.x_min, self.x_max, proportion) def n2p(self, number: float | np.ndarray) -> np.ndarray: """Abbreviation for :meth:`~.NumberLine.number_to_point`.""" return self.number_to_point(number) - def p2n(self, point: Sequence[float]) -> float: + def p2n(self, point: Point3DLike) -> float: """Abbreviation for :meth:`~.NumberLine.point_to_number`.""" return self.point_to_number(point) def get_unit_size(self) -> float: - return self.get_length() / (self.x_range[1] - self.x_range[0]) + val: float = self.get_length() / (self.x_range[1] - self.x_range[0]) + return val def get_unit_vector(self) -> np.ndarray: return super().get_unit_vector() * self.unit_size @@ -436,8 +452,8 @@ def get_number_mobject( direction: Sequence[float] | None = None, buff: float | None = None, font_size: float | None = None, - label_constructor: VMobject | None = None, - **number_config, + label_constructor: type[MathTex] | None = None, + **number_config: dict[str, Any], ) -> VMobject: """Generates a positioned :class:`~.DecimalNumber` mobject generated according to ``label_constructor``. @@ -476,7 +492,12 @@ def get_number_mobject( label_constructor = self.label_constructor num_mob = DecimalNumber( - x, font_size=font_size, mob_class=label_constructor, **number_config + # TODO: + # error: Argument 4 to "DecimalNumber" has incompatible type "**dict[str, dict[str, Any]]"; expected "int" [arg-type] + x, + font_size=font_size, + mob_class=label_constructor, + **number_config, # type: ignore[arg-type] ) num_mob.next_to(self.number_to_point(x), direction=direction, buff=buff) @@ -485,7 +506,7 @@ def get_number_mobject( num_mob.shift(num_mob[0].width * LEFT / 2) return num_mob - def get_number_mobjects(self, *numbers, **kwargs) -> VGroup: + def get_number_mobjects(self, *numbers: float, **kwargs: Any) -> VGroup: if len(numbers) == 0: numbers = self.default_numbers_to_display() return VGroup([self.get_number_mobject(number, **kwargs) for number in numbers]) @@ -498,9 +519,9 @@ def add_numbers( x_values: Iterable[float] | None = None, excluding: Iterable[float] | None = None, font_size: float | None = None, - label_constructor: VMobject | None = None, - **kwargs, - ): + label_constructor: type[MathTex] | None = None, + **kwargs: Any, + ) -> Self: """Adds :class:`~.DecimalNumber` mobjects representing their position at each tick of the number line. The numbers can be accessed after creation via ``self.numbers``. @@ -551,11 +572,11 @@ def add_numbers( def add_labels( self, dict_values: dict[float, str | float | VMobject], - direction: Sequence[float] = None, + direction: Sequence[float] | None = None, buff: float | None = None, font_size: float | None = None, - label_constructor: VMobject | None = None, - ): + label_constructor: type[MathTex] | None = None, + ) -> Self: """Adds specifically positioned labels to the :class:`~.NumberLine` using a ``dict``. The labels can be accessed after creation via ``self.labels``. @@ -598,6 +619,7 @@ def add_labels( label = self._create_label_tex(label, label_constructor) if hasattr(label, "font_size"): + assert isinstance(label, MathTex) label.font_size = font_size else: raise AttributeError(f"{label} is not compatible with add_labels.") @@ -612,7 +634,7 @@ def _create_label_tex( self, label_tex: str | float | VMobject, label_constructor: Callable | None = None, - **kwargs, + **kwargs: Any, ) -> VMobject: """Checks if the label is a :class:`~.VMobject`, otherwise, creates a label by passing ``label_tex`` to ``label_constructor``. @@ -633,24 +655,25 @@ def _create_label_tex( :class:`~.VMobject` The label. """ - if label_constructor is None: - label_constructor = self.label_constructor if isinstance(label_tex, (VMobject, OpenGLVMobject)): return label_tex - else: + if label_constructor is None: + label_constructor = self.label_constructor + if isinstance(label_tex, str): return label_constructor(label_tex, **kwargs) + return label_constructor(str(label_tex), **kwargs) @staticmethod - def _decimal_places_from_step(step) -> int: - step = str(step) - if "." not in step: + def _decimal_places_from_step(step: float) -> int: + step_str = str(step) + if "." not in step_str: return 0 - return len(step.split(".")[-1]) + return len(step_str.split(".")[-1]) - def __matmul__(self, other: float): + def __matmul__(self, other: float) -> np.ndarray: return self.n2p(other) - def __rmatmul__(self, other: Point3DLike | Mobject): + def __rmatmul__(self, other: Point3DLike | Mobject) -> float: if isinstance(other, Mobject): other = other.get_center() return self.p2n(other) @@ -659,11 +682,11 @@ def __rmatmul__(self, other: Point3DLike | Mobject): class UnitInterval(NumberLine): def __init__( self, - unit_size=10, - numbers_with_elongated_ticks=None, - decimal_number_config=None, - **kwargs, - ): + unit_size: float = 10, + numbers_with_elongated_ticks: list[float] | None = None, + decimal_number_config: dict[str, Any] | None = None, + **kwargs: Any, + ) -> None: numbers_with_elongated_ticks = ( [0, 1] if numbers_with_elongated_ticks is None diff --git a/manim/mobject/graphing/scale.py b/manim/mobject/graphing/scale.py index 1a6635727f..173f3d93a3 100644 --- a/manim/mobject/graphing/scale.py +++ b/manim/mobject/graphing/scale.py @@ -11,7 +11,7 @@ from manim.mobject.text.numbers import Integer if TYPE_CHECKING: - from typing import Callable + from typing import Callable, overload from manim.mobject.mobject import Mobject @@ -28,6 +28,12 @@ class _ScaleBase: def __init__(self, custom_labels: bool = False): self.custom_labels = custom_labels + @overload + def function(self, value: float) -> float: ... + + @overload + def function(self, value: np.array) -> np.array: ... + def function(self, value: float) -> float: """The function that will be used to scale the values. @@ -61,6 +67,7 @@ def inverse_function(self, value: float) -> float: def get_custom_labels( self, val_range: Iterable[float], + **kw_args: Any, ) -> Iterable[Mobject]: """Custom instructions for generating labels along an axis. diff --git a/mypy.ini b/mypy.ini index ad355f707c..4dfc8f3c92 100644 --- a/mypy.ini +++ b/mypy.ini @@ -70,6 +70,9 @@ ignore_errors = False [mypy-manim.mobject.graphing.scale.*] ignore_errors = False +[mypy-manim.mobject.graphing.number_line.*] +ignore_errors = False + [mypy-manim.mobject.graphing.probability.*] ignore_errors = False From 859358ee0fee757f6df632af8e8c68bf9064a1a7 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Tue, 21 Jan 2025 07:39:13 +0100 Subject: [PATCH 09/34] Avoid a circular import. --- manim/mobject/graphing/number_line.py | 14 ++++++++++---- manim/mobject/graphing/probability.py | 4 +++- manim/mobject/graphing/scale.py | 4 ++-- manim/mobject/types/image_mobject.py | 3 ++- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/manim/mobject/graphing/number_line.py b/manim/mobject/graphing/number_line.py index f12877521e..2f96a89712 100644 --- a/manim/mobject/graphing/number_line.py +++ b/manim/mobject/graphing/number_line.py @@ -496,7 +496,9 @@ def get_number_mobject( # error: Argument 4 to "DecimalNumber" has incompatible type "**dict[str, dict[str, Any]]"; expected "int" [arg-type] x, font_size=font_size, - mob_class=label_constructor, + # TODO + # error: Argument "mob_class" to "DecimalNumber" has incompatible type "type[MathTex]"; expected "VMobject" [arg-type] + mob_class=label_constructor, # type: ignore[arg-type] **number_config, # type: ignore[arg-type] ) @@ -614,13 +616,17 @@ def add_labels( # this method via CoordinateSystem.add_coordinates() # must be explicitly called if isinstance(label, str) and label_constructor is MathTex: - label = Tex(label) + # TODO + # error: Call to untyped function "Tex" in typed context [no-untyped-call] + label = Tex(label) # type: ignore[no-untyped-call] else: label = self._create_label_tex(label, label_constructor) if hasattr(label, "font_size"): - assert isinstance(label, MathTex) - label.font_size = font_size + # assert isinstance(label, MathTex) + # TODO + # error: "VMobject" has no attribute "font_size" [attr-defined] + label.font_size = font_size # type: ignore[attr-defined] else: raise AttributeError(f"{label} is not compatible with add_labels.") label.next_to(self.number_to_point(x), direction=direction, buff=buff) diff --git a/manim/mobject/graphing/probability.py b/manim/mobject/graphing/probability.py index f8adc83c19..b8440596e5 100644 --- a/manim/mobject/graphing/probability.py +++ b/manim/mobject/graphing/probability.py @@ -78,7 +78,9 @@ def add_title( self, title: str = "Sample space", buff: float = MED_SMALL_BUFF ) -> None: # TODO, should this really exist in SampleSpaceScene - title_mob = Tex(title) + # TODO + # error: Call to untyped function "Tex" in typed context [no-untyped-call] + title_mob = Tex(title) # type: ignore[no-untyped-call] if title_mob.width > self.width: title_mob.width = self.width title_mob.next_to(self, UP, buff=buff) diff --git a/manim/mobject/graphing/scale.py b/manim/mobject/graphing/scale.py index 173f3d93a3..56a02d65b8 100644 --- a/manim/mobject/graphing/scale.py +++ b/manim/mobject/graphing/scale.py @@ -2,7 +2,7 @@ import math from collections.abc import Iterable -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, overload import numpy as np @@ -11,7 +11,7 @@ from manim.mobject.text.numbers import Integer if TYPE_CHECKING: - from typing import Callable, overload + from typing import Callable from manim.mobject.mobject import Mobject diff --git a/manim/mobject/types/image_mobject.py b/manim/mobject/types/image_mobject.py index c0659c672b..8a7d166cfb 100644 --- a/manim/mobject/types/image_mobject.py +++ b/manim/mobject/types/image_mobject.py @@ -14,7 +14,6 @@ from manim.mobject.geometry.shape_matchers import SurroundingRectangle from ... import config -from ...camera.moving_camera import MovingCamera from ...constants import * from ...mobject.mobject import Mobject from ...utils.bezier import interpolate @@ -31,6 +30,8 @@ from manim.typing import PixelArray, StrPath + from ...camera.moving_camera import MovingCamera + class AbstractImageMobject(Mobject): """ From f8e1bae748dbe60cf1e1a17dafbac83d7805f7d2 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Fri, 17 Jan 2025 23:10:21 +0100 Subject: [PATCH 10/34] Add type annotations to mobject/graphing/functions.py --- manim/mobject/graphing/functions.py | 37 +++++++++++++++++++---------- mypy.ini | 3 +++ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/manim/mobject/graphing/functions.py b/manim/mobject/graphing/functions.py index 83c48b1092..5cf406dd22 100644 --- a/manim/mobject/graphing/functions.py +++ b/manim/mobject/graphing/functions.py @@ -17,9 +17,12 @@ from manim.mobject.types.vectorized_mobject import VMobject if TYPE_CHECKING: + from typing import Any + from typing_extensions import Self from manim.typing import Point3D, Point3DLike + from manim.utils.color import ParsableManimColor from manim.utils.color import YELLOW @@ -111,7 +114,7 @@ def __init__( discontinuities: Iterable[float] | None = None, use_smoothing: bool = True, use_vectorized: bool = False, - **kwargs, + **kwargs: Any, ): def internal_parametric_function(t: float) -> Point3D: """Wrap ``function``'s output inside a NumPy array.""" @@ -143,13 +146,13 @@ def generate_points(self) -> Self: lambda t: self.t_min <= t <= self.t_max, self.discontinuities, ) - discontinuities = np.array(list(discontinuities)) + discontinuities_array = np.array(list(discontinuities)) boundary_times = np.array( [ self.t_min, self.t_max, - *(discontinuities - self.dt), - *(discontinuities + self.dt), + *(discontinuities_array - self.dt), + *(discontinuities_array + self.dt), ], ) boundary_times.sort() @@ -211,19 +214,29 @@ def construct(self): self.add(cos_func, sin_func_1, sin_func_2) """ - def __init__(self, function, x_range=None, color=YELLOW, **kwargs): + def __init__( + self, + function: Callable[[float], Any], + x_range: np.array | None = None, + color: ParsableManimColor = YELLOW, + **kwargs: Any, + ) -> None: if x_range is None: x_range = np.array([-config["frame_x_radius"], config["frame_x_radius"]]) self.x_range = x_range - self.parametric_function = lambda t: np.array([t, function(t), 0]) - self.function = function + self.parametric_function: Callable[[float], np.array] = lambda t: np.array( + [t, function(t), 0] + ) + # TODO: + # error: Incompatible types in assignment (expression has type "Callable[[float], Any]", variable has type "Callable[[Arg(float, 't')], Any]") [assignment] + self.function = function # type: ignore[assignment] super().__init__(self.parametric_function, self.x_range, color=color, **kwargs) - def get_function(self): + def get_function(self) -> Callable[[float], Any]: return self.function - def get_point_from_function(self, x): + def get_point_from_function(self, x: float) -> np.array: return self.parametric_function(x) @@ -236,8 +249,8 @@ def __init__( min_depth: int = 5, max_quads: int = 1500, use_smoothing: bool = True, - **kwargs, - ): + **kwargs: Any, + ) -> None: """An implicit function. Parameters @@ -295,7 +308,7 @@ def construct(self): super().__init__(**kwargs) - def generate_points(self): + def generate_points(self) -> Self: p_min, p_max = ( np.array([self.x_range[0], self.y_range[0]]), np.array([self.x_range[1], self.y_range[1]]), diff --git a/mypy.ini b/mypy.ini index 4dfc8f3c92..4e6e3fcbf8 100644 --- a/mypy.ini +++ b/mypy.ini @@ -70,6 +70,9 @@ ignore_errors = False [mypy-manim.mobject.graphing.scale.*] ignore_errors = False +[mypy-manim.mobject.graphing.functions.*] +ignore_errors = False + [mypy-manim.mobject.graphing.number_line.*] ignore_errors = False From d5d3928e308d119213e1e4a5a3c425d94a63d83c Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Mon, 20 Jan 2025 21:54:20 +0100 Subject: [PATCH 11/34] In progress: Add typehints to graphing/coordinate_systems.py part1 --- manim/mobject/graphing/coordinate_systems.py | 32 +++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/manim/mobject/graphing/coordinate_systems.py b/manim/mobject/graphing/coordinate_systems.py index b21879b90b..d828a66c99 100644 --- a/manim/mobject/graphing/coordinate_systems.py +++ b/manim/mobject/graphing/coordinate_systems.py @@ -153,11 +153,12 @@ def __init__( self.x_length = x_length self.y_length = y_length self.num_sampled_graph_points_per_tick = 10 + self.x_axis: NumberLine - def coords_to_point(self, *coords: ManimFloat): + def coords_to_point(self, *coords: ManimFloat) -> Point3DLike: raise NotImplementedError() - def point_to_coords(self, point: Point3DLike): + def point_to_coords(self, point: Point3DLike) -> list[ManimFloat]: raise NotImplementedError() def polar_to_point(self, radius: float, azimuth: float) -> Point2D: @@ -213,7 +214,7 @@ def c2p( """Abbreviation for :meth:`coords_to_point`""" return self.coords_to_point(*coords) - def p2c(self, point: Point3DLike): + def p2c(self, point: Point3DLike) -> list[ManimFloat]: """Abbreviation for :meth:`point_to_coords`""" return self.point_to_coords(point) @@ -221,17 +222,18 @@ def pr2pt(self, radius: float, azimuth: float) -> np.ndarray: """Abbreviation for :meth:`polar_to_point`""" return self.polar_to_point(radius, azimuth) - def pt2pr(self, point: np.ndarray) -> tuple[float, float]: + def pt2pr(self, point: np.ndarray) -> Point2D: """Abbreviation for :meth:`point_to_polar`""" return self.point_to_polar(point) - def get_axes(self): + def get_axes(self) -> VGroup: raise NotImplementedError() - def get_axis(self, index: int) -> Mobject: - return self.get_axes()[index] + def get_axis(self, index: int) -> NumberLine: + val: NumberLine = self.get_axes()[index] + return val - def get_origin(self) -> np.ndarray: + def get_origin(self) -> Point3DLike: """Gets the origin of :class:`~.Axes`. Returns @@ -241,13 +243,13 @@ def get_origin(self) -> np.ndarray: """ return self.coords_to_point(0, 0) - def get_x_axis(self) -> Mobject: + def get_x_axis(self) -> NumberLine: return self.get_axis(0) - def get_y_axis(self) -> Mobject: + def get_y_axis(self) -> NumberLine: return self.get_axis(1) - def get_z_axis(self) -> Mobject: + def get_z_axis(self) -> NumberLine: return self.get_axis(2) def get_x_unit_size(self) -> float: @@ -258,11 +260,11 @@ def get_y_unit_size(self) -> float: def get_x_axis_label( self, - label: float | str | Mobject, + label: float | str | VMobject, edge: Sequence[float] = UR, direction: Sequence[float] = UR, buff: float = SMALL_BUFF, - **kwargs, + **kwargs: Any, ) -> Mobject: """Generate an x-axis label. @@ -301,11 +303,11 @@ def construct(self): def get_y_axis_label( self, - label: float | str | Mobject, + label: float | str | VMobject, edge: Sequence[float] = UR, direction: Sequence[float] = UP * 0.5 + RIGHT, buff: float = SMALL_BUFF, - **kwargs, + **kwargs: Any, ) -> Mobject: """Generate a y-axis label. From 3969b7ea448a35ea6af22a03d83acfffdaf59cf8 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Tue, 21 Jan 2025 07:49:49 +0100 Subject: [PATCH 12/34] In progress: Add typehints to graphing/coordinate_systems.py part 2 --- manim/mobject/graphing/coordinate_systems.py | 104 +++++++++++-------- 1 file changed, 59 insertions(+), 45 deletions(-) diff --git a/manim/mobject/graphing/coordinate_systems.py b/manim/mobject/graphing/coordinate_systems.py index d828a66c99..24bb42e8b8 100644 --- a/manim/mobject/graphing/coordinate_systems.py +++ b/manim/mobject/graphing/coordinate_systems.py @@ -349,7 +349,7 @@ def construct(self): def _get_axis_label( self, - label: float | str | Mobject, + label: float | str | VMobject, axis: Mobject, edge: Sequence[float], direction: Sequence[float], @@ -375,12 +375,14 @@ def _get_axis_label( :class:`~.Mobject` The positioned label along the given axis. """ - label = self.x_axis._create_label_tex(label) - label.next_to(axis.get_edge_center(edge), direction=direction, buff=buff) - label.shift_onto_screen(buff=MED_SMALL_BUFF) - return label + label_mobject: Mobject = self.x_axis._create_label_tex(label) + label_mobject.next_to( + axis.get_edge_center(edge), direction=direction, buff=buff + ) + label_mobject.shift_onto_screen(buff=MED_SMALL_BUFF) + return label_mobject - def get_axis_labels(self): + def get_axis_labels(self) -> VGroup: raise NotImplementedError() def add_coordinates( @@ -554,7 +556,7 @@ def construct(self): """ return self.get_line_from_axis_to_point(0, point, **kwargs) - def get_horizontal_line(self, point: Sequence[float], **kwargs) -> Line: + def get_horizontal_line(self, point: Sequence[float], **kwargs: Any) -> Line: """A horizontal line from the y-axis to a given point in the scene. Parameters @@ -586,7 +588,7 @@ def construct(self): """ return self.get_line_from_axis_to_point(1, point, **kwargs) - def get_lines_to_point(self, point: Sequence[float], **kwargs) -> VGroup: + def get_lines_to_point(self, point: Sequence[float], **kwargs: Any) -> VGroup: """Generate both horizontal and vertical lines from the axis to a point. Parameters @@ -1095,7 +1097,7 @@ def i2gp(self, x: float, graph: ParametricFunction) -> np.ndarray: def get_graph_label( self, graph: ParametricFunction, - label: float | str | Mobject = "f(x)", + label: float | str | VMobject = "f(x)", x_val: float | None = None, direction: Sequence[float] = RIGHT, buff: float = MED_SMALL_BUFF, @@ -1152,7 +1154,7 @@ def construct(self): dot_config = {} if color is None: color = graph.get_color() - label = self.x_axis._create_label_tex(label).set_color(color) + label_object: Mobject = self.x_axis._create_label_tex(label).set_color(color) if x_val is None: # Search from right to left @@ -1163,14 +1165,14 @@ def construct(self): else: point = self.input_to_graph_point(x_val, graph) - label.next_to(point, direction, buff=buff) - label.shift_onto_screen() + label_object.next_to(point, direction, buff=buff) + label_object.shift_onto_screen() if dot: dot = Dot(point=point, **dot_config) - label.add(dot) - label.dot = dot - return label + label_object.add(dot) + label_object.dot = dot + return label_object # calculus @@ -1178,14 +1180,14 @@ def get_riemann_rectangles( self, graph: ParametricFunction, x_range: Sequence[float] | None = None, - dx: float | None = 0.1, + dx: float = 0.1, input_sample_type: str = "left", stroke_width: float = 1, stroke_color: ParsableManimColor = BLACK, fill_opacity: float = 1, color: Iterable[ParsableManimColor] | ParsableManimColor = (BLUE, GREEN), show_signed_area: bool = True, - bounded_graph: ParametricFunction = None, + bounded_graph: ParametricFunction | None = None, blend: bool = False, width_scale_factor: float = 1.001, ) -> VGroup: @@ -1279,16 +1281,16 @@ def construct(self): x_range = [*x_range[:2], dx] rectangles = VGroup() - x_range = np.arange(*x_range) + x_range_array = np.arange(*x_range) if isinstance(color, (list, tuple)): color = [ManimColor(c) for c in color] else: color = [ManimColor(color)] - colors = color_gradient(color, len(x_range)) + colors = color_gradient(color, len(x_range_array)) - for x, color in zip(x_range, colors): + for x, color in zip(x_range_array, colors): if input_sample_type == "left": sample_input = x elif input_sample_type == "right": @@ -1343,7 +1345,7 @@ def get_area( x_range: tuple[float, float] | None = None, color: ParsableManimColor | Iterable[ParsableManimColor] = (BLUE, GREEN), opacity: float = 0.3, - bounded_graph: ParametricFunction = None, + bounded_graph: ParametricFunction | None = None, **kwargs: Any, ) -> Polygon: """Returns a :class:`~.Polygon` representing the area under the graph passed. @@ -1487,10 +1489,14 @@ def slope_of_tangent( ax.slope_of_tangent(x=-2, graph=curve) # -3.5000000259052038 """ - return np.tan(self.angle_of_tangent(x, graph, **kwargs)) + val: float = np.tan(self.angle_of_tangent(x, graph, **kwargs)) + return val def plot_derivative_graph( - self, graph: ParametricFunction, color: ParsableManimColor = GREEN, **kwargs + self, + graph: ParametricFunction, + color: ParsableManimColor = GREEN, + **kwargs: Any, ) -> ParametricFunction: """Returns the curve of the derivative of the passed graph. @@ -1528,7 +1534,7 @@ def construct(self): self.add(ax, curves, labels) """ - def deriv(x): + def deriv(x: float) -> float: return self.slope_of_tangent(x, graph) return self.plot(deriv, color=color, **kwargs) @@ -1845,14 +1851,17 @@ def construct(self): return T_label_group - def __matmul__(self, coord: Point3DLike | Mobject): + def __matmul__(self, coord: Point3DLike | Mobject) -> Point3DLike: if isinstance(coord, Mobject): coord = coord.get_center() return self.coords_to_point(*coord) - def __rmatmul__(self, point: Point3DLike): + def __rmatmul__(self, point: Point3DLike) -> Point3DLike: return self.point_to_coords(point) + @staticmethod + def _origin_shift(axis_range: Sequence[float]) -> float: ... + class Axes(VGroup, CoordinateSystem, metaclass=ConvertToOpenGL): """Creates a set of axes. @@ -1928,8 +1937,11 @@ def __init__( "include_tip": tips, "numbers_to_exclude": [0], } - self.x_axis_config = {} - self.y_axis_config = {"rotation": 90 * DEGREES, "label_direction": LEFT} + self.x_axis_config: dict[str, Any] = {} + self.y_axis_config: dict[str, Any] = { + "rotation": 90 * DEGREES, + "label_direction": LEFT, + } self._update_default_configs( (self.axis_config, self.x_axis_config, self.y_axis_config), @@ -2420,8 +2432,8 @@ def __init__( num_axis_pieces: int = 20, light_source: Sequence[float] = 9 * DOWN + 7 * LEFT + 10 * OUT, # opengl stuff (?) - depth=None, - gloss=0.5, + depth: Any = None, + gloss: float = 0.5, **kwargs: dict[str, Any], ) -> None: super().__init__( @@ -2435,7 +2447,7 @@ def __init__( self.z_range = z_range self.z_length = z_length - self.z_axis_config = {} + self.z_axis_config: dict[str, Any] = {} self._update_default_configs((self.z_axis_config,), (z_axis_config,)) self.z_axis_config = merge_dicts_recursively( self.axis_config, @@ -2502,13 +2514,13 @@ def make_func(axis): def get_y_axis_label( self, - label: float | str | Mobject, + label: float | str | VMobject, edge: Sequence[float] = UR, direction: Sequence[float] = UR, buff: float = SMALL_BUFF, rotation: float = PI / 2, rotation_axis: Vector3D = OUT, - **kwargs, + **kwargs: dict[str, Any], ) -> Mobject: """Generate a y-axis label. @@ -2552,7 +2564,7 @@ def construct(self): def get_z_axis_label( self, - label: float | str | Mobject, + label: float | str | VMobject, edge: Vector3D = OUT, direction: Vector3D = RIGHT, buff: float = SMALL_BUFF, @@ -2602,9 +2614,9 @@ def construct(self): def get_axis_labels( self, - x_label: float | str | Mobject = "x", - y_label: float | str | Mobject = "y", - z_label: float | str | Mobject = "z", + x_label: float | str | VMobject = "x", + y_label: float | str | VMobject = "y", + z_label: float | str | VMobject = "z", ) -> VGroup: """Defines labels for the x_axis and y_axis of the graph. @@ -2743,7 +2755,7 @@ def __init__( **kwargs: dict[str, Any], ): # configs - self.axis_config = { + self.axis_config: dict[str, Any] = { "stroke_width": 2, "include_ticks": False, "include_tip": False, @@ -2751,8 +2763,8 @@ def __init__( "label_direction": DR, "font_size": 24, } - self.y_axis_config = {"label_direction": DR} - self.background_line_style = { + self.y_axis_config: dict[str, Any] = {"label_direction": DR} + self.background_line_style: dict[str, Any] = { "stroke_color": BLUE_D, "stroke_width": 2, "stroke_opacity": 1, @@ -2999,7 +3011,7 @@ def __init__( size: float | None = None, radius_step: float = 1, azimuth_step: float | None = None, - azimuth_units: str | None = "PI radians", + azimuth_units: str = "PI radians", azimuth_compact_fraction: bool = True, azimuth_offset: float = 0, azimuth_direction: str = "CCW", @@ -3132,11 +3144,11 @@ def _get_lines(self) -> tuple[VGroup, VGroup]: unit_vector = self.x_axis.get_unit_vector()[0] for k, x in enumerate(rinput): - new_line = Circle(radius=x * unit_vector) + new_circle = Circle(radius=x * unit_vector) if k % ratio_faded_lines == 0: - alines1.add(new_line) + alines1.add(new_circle) else: - alines2.add(new_line) + alines2.add(new_circle) line = Line(center, self.get_x_axis().get_end()) @@ -3294,7 +3306,9 @@ def add_coordinates( self.add(self.get_coordinate_labels(r_values, a_values)) return self - def get_radian_label(self, number, font_size: float = 24, **kwargs: Any) -> MathTex: + def get_radian_label( + self, number: float, font_size: float = 24, **kwargs: Any + ) -> MathTex: constant_label = {"PI radians": r"\pi", "TAU radians": r"\tau"}[ self.azimuth_units ] From 90f0decc250f37370bcd3c680dd75fb48d8953b1 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Fri, 17 Jan 2025 21:19:11 +0100 Subject: [PATCH 13/34] Add type annotations to manim/camera/multi_camera.py - part 1 --- manim/camera/camera.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/manim/camera/camera.py b/manim/camera/camera.py index 700c164861..9da835a01c 100644 --- a/manim/camera/camera.py +++ b/manim/camera/camera.py @@ -10,7 +10,7 @@ import pathlib from collections.abc import Iterable from functools import reduce -from typing import Any, Callable +from typing import TYPE_CHECKING, Any, Callable import cairo import numpy as np @@ -23,7 +23,6 @@ from .. import config, logger from ..constants import * from ..mobject.mobject import Mobject -from ..mobject.types.image_mobject import AbstractImageMobject from ..mobject.types.point_cloud_mobject import PMobject from ..mobject.types.vectorized_mobject import VMobject from ..utils.color import ManimColor, ParsableManimColor, color_to_int_rgba @@ -32,6 +31,10 @@ from ..utils.iterables import list_difference_update from ..utils.space_ops import angle_of_vector +if TYPE_CHECKING: + from ..mobject.types.image_mobject import AbstractImageMobject + + LINE_JOIN_MAP = { LineJointType.AUTO: None, # TODO: this could be improved LineJointType.ROUND: cairo.LineJoin.ROUND, @@ -199,6 +202,8 @@ def type_or_raise( :exc:`TypeError` When mobject is not an instance of a class that can be rendered. """ + from ..mobject.types.image_mobject import AbstractImageMobject + self.display_funcs = { VMobject: self.display_multiple_vectorized_mobjects, PMobject: self.display_multiple_point_cloud_mobjects, From b1a3508cf50250bdd34dee4065cadc00827d3a0d Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Tue, 21 Jan 2025 07:57:06 +0100 Subject: [PATCH 14/34] Add type annotations to manim/camera/multi_camera.py - part 2 --- manim/camera/multi_camera.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/manim/camera/multi_camera.py b/manim/camera/multi_camera.py index a5202135e9..44da140db7 100644 --- a/manim/camera/multi_camera.py +++ b/manim/camera/multi_camera.py @@ -5,7 +5,13 @@ __all__ = ["MultiCamera"] -from manim.mobject.types.image_mobject import ImageMobject +from collections.abc import Iterable +from typing import Any + +from typing_extensions import Self + +from manim.mobject.mobject import Mobject +from manim.mobject.types.image_mobject import ImageMobjectFromCamera from ..camera.moving_camera import MovingCamera from ..utils.iterables import list_difference_update @@ -16,10 +22,10 @@ class MultiCamera(MovingCamera): def __init__( self, - image_mobjects_from_cameras: ImageMobject | None = None, - allow_cameras_to_capture_their_own_display=False, - **kwargs, - ): + image_mobjects_from_cameras: Iterable[ImageMobjectFromCamera] | None = None, + allow_cameras_to_capture_their_own_display: bool = False, + **kwargs: Any, + ) -> None: """Initialises the MultiCamera Parameters @@ -29,7 +35,7 @@ def __init__( kwargs Any valid keyword arguments of MovingCamera. """ - self.image_mobjects_from_cameras = [] + self.image_mobjects_from_cameras: list[ImageMobjectFromCamera] = [] if image_mobjects_from_cameras is not None: for imfc in image_mobjects_from_cameras: self.add_image_mobject_from_camera(imfc) @@ -38,7 +44,9 @@ def __init__( ) super().__init__(**kwargs) - def add_image_mobject_from_camera(self, image_mobject_from_camera: ImageMobject): + def add_image_mobject_from_camera( + self, image_mobject_from_camera: ImageMobjectFromCamera + ) -> None: """Adds an ImageMobject that's been obtained from the camera into the list ``self.image_mobject_from_cameras`` @@ -53,7 +61,7 @@ def add_image_mobject_from_camera(self, image_mobject_from_camera: ImageMobject) assert isinstance(imfc.camera, MovingCamera) self.image_mobjects_from_cameras.append(imfc) - def update_sub_cameras(self): + def update_sub_cameras(self) -> None: """Reshape sub_camera pixel_arrays""" for imfc in self.image_mobjects_from_cameras: pixel_height, pixel_width = self.pixel_array.shape[:2] @@ -66,7 +74,7 @@ def update_sub_cameras(self): int(pixel_width * imfc.width / self.frame_width), ) - def reset(self): + def reset(self) -> Self: """Resets the MultiCamera. Returns @@ -79,7 +87,7 @@ def reset(self): super().reset() return self - def capture_mobjects(self, mobjects, **kwargs): + def capture_mobjects(self, mobjects: Iterable[Mobject], **kwargs: Any) -> None: self.update_sub_cameras() for imfc in self.image_mobjects_from_cameras: to_add = list(mobjects) @@ -88,7 +96,7 @@ def capture_mobjects(self, mobjects, **kwargs): imfc.camera.capture_mobjects(to_add, **kwargs) super().capture_mobjects(mobjects, **kwargs) - def get_mobjects_indicating_movement(self): + def get_mobjects_indicating_movement(self) -> list[Mobject]: """Returns all mobjects whose movement implies that the camera should think of all other mobjects on the screen as moving From f767e8a51b3957b354c2a39efa7cd8ba62a7f1f8 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Tue, 21 Jan 2025 07:57:28 +0100 Subject: [PATCH 15/34] Add type annotations to manim/camera/multi_camera.py - part 3 --- manim/camera/multi_camera.py | 4 +++- manim/mobject/types/image_mobject.py | 1 + mypy.ini | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/manim/camera/multi_camera.py b/manim/camera/multi_camera.py index 44da140db7..fe032e4668 100644 --- a/manim/camera/multi_camera.py +++ b/manim/camera/multi_camera.py @@ -65,7 +65,9 @@ def update_sub_cameras(self) -> None: """Reshape sub_camera pixel_arrays""" for imfc in self.image_mobjects_from_cameras: pixel_height, pixel_width = self.pixel_array.shape[:2] - imfc.camera.frame_shape = ( + # TODO: + # error: "MovingCamera" has no attribute "frame_shape" [attr-defined] + imfc.camera.frame_shape = ( # type: ignore[attr-defined] imfc.camera.frame.height, imfc.camera.frame.width, ) diff --git a/manim/mobject/types/image_mobject.py b/manim/mobject/types/image_mobject.py index 8a7d166cfb..f2fb9d649f 100644 --- a/manim/mobject/types/image_mobject.py +++ b/manim/mobject/types/image_mobject.py @@ -14,6 +14,7 @@ from manim.mobject.geometry.shape_matchers import SurroundingRectangle from ... import config +from ...camera.moving_camera import MovingCamera from ...constants import * from ...mobject.mobject import Mobject from ...utils.bezier import interpolate diff --git a/mypy.ini b/mypy.ini index 4e6e3fcbf8..4d81f2c7b9 100644 --- a/mypy.ini +++ b/mypy.ini @@ -61,6 +61,9 @@ ignore_errors = True [mypy-manim.camera.camera.*] ignore_errors = False +[mypy-manim.camera.multi_camera.*] +ignore_errors = False + [mypy-manim.cli.*] ignore_errors = False From 9db34efbf13a930b20e9cc3958de29d6b66c2ed7 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Wed, 30 Jul 2025 09:39:34 +0200 Subject: [PATCH 16/34] Reducing the scope of the PR and fixed a number of small items --- manim/mobject/graphing/number_line.py | 19 +++++-------------- manim/mobject/graphing/probability.py | 4 +--- manim/mobject/graphing/scale.py | 8 +++----- mypy.ini | 3 +++ 4 files changed, 12 insertions(+), 22 deletions(-) diff --git a/manim/mobject/graphing/number_line.py b/manim/mobject/graphing/number_line.py index 2f96a89712..660d8d524e 100644 --- a/manim/mobject/graphing/number_line.py +++ b/manim/mobject/graphing/number_line.py @@ -478,7 +478,7 @@ def get_number_mobject( :class:`~.DecimalNumber` The positioned mobject. """ - number_config = merge_dicts_recursively( + number_config_merged = merge_dicts_recursively( self.decimal_number_config, number_config, ) @@ -492,14 +492,10 @@ def get_number_mobject( label_constructor = self.label_constructor num_mob = DecimalNumber( - # TODO: - # error: Argument 4 to "DecimalNumber" has incompatible type "**dict[str, dict[str, Any]]"; expected "int" [arg-type] x, font_size=font_size, - # TODO - # error: Argument "mob_class" to "DecimalNumber" has incompatible type "type[MathTex]"; expected "VMobject" [arg-type] - mob_class=label_constructor, # type: ignore[arg-type] - **number_config, # type: ignore[arg-type] + mob_class=label_constructor, + **number_config_merged, ) num_mob.next_to(self.number_to_point(x), direction=direction, buff=buff) @@ -616,17 +612,12 @@ def add_labels( # this method via CoordinateSystem.add_coordinates() # must be explicitly called if isinstance(label, str) and label_constructor is MathTex: - # TODO - # error: Call to untyped function "Tex" in typed context [no-untyped-call] - label = Tex(label) # type: ignore[no-untyped-call] + label = Tex(label) else: label = self._create_label_tex(label, label_constructor) if hasattr(label, "font_size"): - # assert isinstance(label, MathTex) - # TODO - # error: "VMobject" has no attribute "font_size" [attr-defined] - label.font_size = font_size # type: ignore[attr-defined] + label.font_size = font_size else: raise AttributeError(f"{label} is not compatible with add_labels.") label.next_to(self.number_to_point(x), direction=direction, buff=buff) diff --git a/manim/mobject/graphing/probability.py b/manim/mobject/graphing/probability.py index b8440596e5..f8adc83c19 100644 --- a/manim/mobject/graphing/probability.py +++ b/manim/mobject/graphing/probability.py @@ -78,9 +78,7 @@ def add_title( self, title: str = "Sample space", buff: float = MED_SMALL_BUFF ) -> None: # TODO, should this really exist in SampleSpaceScene - # TODO - # error: Call to untyped function "Tex" in typed context [no-untyped-call] - title_mob = Tex(title) # type: ignore[no-untyped-call] + title_mob = Tex(title) if title_mob.width > self.width: title_mob.width = self.width title_mob.next_to(self, UP, buff=buff) diff --git a/manim/mobject/graphing/scale.py b/manim/mobject/graphing/scale.py index 40ca65f497..9db95541db 100644 --- a/manim/mobject/graphing/scale.py +++ b/manim/mobject/graphing/scale.py @@ -163,7 +163,7 @@ def func(value: float, base: float) -> float: return return_value else: condition = value <= 0 - func = math.log # type: ignore[assignment] + func = math.log if condition: raise ValueError( @@ -190,13 +190,11 @@ def get_custom_labels( Additional arguments to be passed to :class:`~.Integer`. """ # uses `format` syntax to control the number of decimal places. - tex_labels: list[Mobject] = [ + tex_labels: list[Integer] = [ Integer( self.base, unit="^{%s}" % (f"{self.inverse_function(i):.{unit_decimal_places}f}"), # noqa: UP031 - # TODO: - # error: Argument 3 to "Integer" has incompatible type "**dict[str, dict[str, Any]]"; expected "int" [arg-type] - **base_config, # type: ignore[arg-type] + **base_config, ) for i in val_range ] diff --git a/mypy.ini b/mypy.ini index 32e6632545..43433b088d 100644 --- a/mypy.ini +++ b/mypy.ini @@ -90,6 +90,9 @@ ignore_errors = True [mypy-manim.camera.moving_camera] ignore_errors = True +[mypy-manim.mobject.graphing.coordinate_systems] +ignore_errors = True + [mypy-manim.mobject.graph] ignore_errors = True From 3ff1095f26b66f19c72445e6ccb875259c97f89b Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Wed, 30 Jul 2025 10:26:14 +0200 Subject: [PATCH 17/34] Removing type ignore statements. --- manim/camera/camera.py | 8 +++---- manim/camera/multi_camera.py | 4 +--- manim/mobject/graphing/functions.py | 4 +--- manim/mobject/graphing/number_line.py | 14 +++++------ manim/mobject/graphing/probability.py | 34 ++++++++++++--------------- manim/mobject/graphing/scale.py | 4 ++-- manim/mobject/text/tex_mobject.py | 3 --- 7 files changed, 29 insertions(+), 42 deletions(-) diff --git a/manim/camera/camera.py b/manim/camera/camera.py index df78dbc77a..43c66a53e3 100644 --- a/manim/camera/camera.py +++ b/manim/camera/camera.py @@ -204,7 +204,9 @@ def type_or_raise( """ from ..mobject.types.image_mobject import AbstractImageMobject - self.display_funcs = { + self.display_funcs: dict[ + type[Mobject], Callable[[list[Mobject], PixelArray], None] + ] = { VMobject: self.display_multiple_vectorized_mobjects, PMobject: self.display_multiple_point_cloud_mobjects, AbstractImageMobject: self.display_multiple_image_mobjects, @@ -541,9 +543,7 @@ def capture_mobjects(self, mobjects: Iterable[Mobject], **kwargs: Any) -> None: # partition while at the same time preserving order. mobjects = self.get_mobjects_to_display(mobjects, **kwargs) for group_type, group in it.groupby(mobjects, self.type_or_raise): - # TODO - # error: Call to untyped function (unknown) in typed context [no-untyped-call] - self.display_funcs[group_type](list(group), self.pixel_array) # type: ignore[no-untyped-call] + self.display_funcs[group_type](list(group), self.pixel_array) # Methods associated with svg rendering diff --git a/manim/camera/multi_camera.py b/manim/camera/multi_camera.py index fe032e4668..44da140db7 100644 --- a/manim/camera/multi_camera.py +++ b/manim/camera/multi_camera.py @@ -65,9 +65,7 @@ def update_sub_cameras(self) -> None: """Reshape sub_camera pixel_arrays""" for imfc in self.image_mobjects_from_cameras: pixel_height, pixel_width = self.pixel_array.shape[:2] - # TODO: - # error: "MovingCamera" has no attribute "frame_shape" [attr-defined] - imfc.camera.frame_shape = ( # type: ignore[attr-defined] + imfc.camera.frame_shape = ( imfc.camera.frame.height, imfc.camera.frame.width, ) diff --git a/manim/mobject/graphing/functions.py b/manim/mobject/graphing/functions.py index 5cf406dd22..122d5cf8fb 100644 --- a/manim/mobject/graphing/functions.py +++ b/manim/mobject/graphing/functions.py @@ -228,9 +228,7 @@ def __init__( self.parametric_function: Callable[[float], np.array] = lambda t: np.array( [t, function(t), 0] ) - # TODO: - # error: Incompatible types in assignment (expression has type "Callable[[float], Any]", variable has type "Callable[[Arg(float, 't')], Any]") [assignment] - self.function = function # type: ignore[assignment] + self.function: Callable[[float], Any] = function super().__init__(self.parametric_function, self.x_range, color=color, **kwargs) def get_function(self) -> Callable[[float], Any]: diff --git a/manim/mobject/graphing/number_line.py b/manim/mobject/graphing/number_line.py index 660d8d524e..647b9101b0 100644 --- a/manim/mobject/graphing/number_line.py +++ b/manim/mobject/graphing/number_line.py @@ -253,18 +253,16 @@ def __init__( if self.scaling.custom_labels: tick_range = self.get_tick_range() + custom_labels = self.scaling.get_custom_labels( + tick_range, + unit_decimal_places=decimal_number_config["num_decimal_places"], + ) + self.add_labels( dict( zip( tick_range, - # TODO: - # Argument 2 to "zip" has incompatible type "Iterable[Mobject]"; expected "Iterable[str | float | VMobject]" [arg-type] - self.scaling.get_custom_labels( # type: ignore[arg-type] - tick_range, - unit_decimal_places=decimal_number_config[ - "num_decimal_places" - ], - ), + custom_labels, ) ), ) diff --git a/manim/mobject/graphing/probability.py b/manim/mobject/graphing/probability.py index f8adc83c19..d70f5fb88e 100644 --- a/manim/mobject/graphing/probability.py +++ b/manim/mobject/graphing/probability.py @@ -102,8 +102,14 @@ def get_division_along_dimension( colors: Sequence[ParsableManimColor], vect: Vector3D, ) -> VGroup: + # Consideration, can we somehow describe that a VGroup only + # contains objects of a certain kind? + # For this function I would like to describe the return type as + # VGroup[SampleSpace]. p_list_complete = self.complete_p_list(p_list) colors_in_gradient = color_gradient(colors, len(p_list)) + + # TODO: Is this needed? assert isinstance(colors_in_gradient, list) last_point = self.get_edge_center(-vect) @@ -134,15 +140,11 @@ def get_vertical_division( ) -> VGroup: return self.get_division_along_dimension(p_list, 0, colors, vect) - # TODO: - # error: Function is missing a type annotation for one or more arguments [no-untyped-def] - def divide_horizontally(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def] + def divide_horizontally(self, *args: Any, **kwargs: Any) -> None: self.horizontal_parts = self.get_horizontal_division(*args, **kwargs) self.add(self.horizontal_parts) - # TODO: - # error: Function is missing a type annotation for one or more arguments [no-untyped-def] - def divide_vertically(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def] + def divide_vertically(self, *args: Any, **kwargs: Any) -> None: self.vertical_parts = self.get_vertical_division(*args, **kwargs) self.add(self.vertical_parts) @@ -166,14 +168,10 @@ def get_subdivision_braces_and_labels( label_mob.next_to(brace, direction, buff) braces.add(brace) - # TODO: - # error: Argument 1 to "add" of "VGroup" has incompatible type "Mobject | OpenGLMobject"; expected "VMobject | Iterable[VMobject]" [arg-type] - label_mobs.add(label_mob) # type: ignore[arg-type] - # TODO: - # error: "VGroup" has no attribute "braces" [attr-defined] - parts.braces = braces # type: ignore[attr-defined] - parts.labels = label_mobs # type: ignore[attr-defined] - parts.label_kwargs = { # type: ignore[attr-defined] + label_mobs.add(label_mob) + parts.braces = braces + parts.labels = label_mobs + parts.label_kwargs = { "labels": label_mobs.copy(), "direction": direction, "buff": buff, @@ -215,16 +213,14 @@ def add_braces_and_labels(self) -> None: if hasattr(parts, subattr): self.add(getattr(parts, subattr)) - def __getitem__(self, index: int) -> VGroup: + def __getitem__(self, index: int) -> SampleSpace: if hasattr(self, "horizontal_parts"): - val: VGroup = self.horizontal_parts[index] + val: SampleSpace = self.horizontal_parts[index] return val elif hasattr(self, "vertical_parts"): val = self.vertical_parts[index] return val - # TODO: - # error: Incompatible return value type (got "SampleSpace", expected "VGroup") [return-value] - return self.split()[index] # type: ignore[return-value] + return self.split()[index] class BarChart(Axes): diff --git a/manim/mobject/graphing/scale.py b/manim/mobject/graphing/scale.py index 9db95541db..d894a320eb 100644 --- a/manim/mobject/graphing/scale.py +++ b/manim/mobject/graphing/scale.py @@ -13,7 +13,7 @@ if TYPE_CHECKING: from typing import Callable - from manim.mobject.mobject import Mobject + from manim.mobject.types.vectorized_mobject import VMobject class _ScaleBase: @@ -68,7 +68,7 @@ def get_custom_labels( self, val_range: Iterable[float], **kw_args: Any, - ) -> Iterable[Mobject]: + ) -> Iterable[VMobject]: """Custom instructions for generating labels along an axis. Parameters diff --git a/manim/mobject/text/tex_mobject.py b/manim/mobject/text/tex_mobject.py index db298e62e7..1eb78d9adc 100644 --- a/manim/mobject/text/tex_mobject.py +++ b/manim/mobject/text/tex_mobject.py @@ -1,6 +1,3 @@ -# The following line is needed to avoid some strange -# mypy errors related to the code around line 366. -# mypy: disable_error_code = has-type r"""Mobjects representing text rendered using LaTeX. .. important:: From 967ef1e98925497c5983a396cd2b912ca46c3477 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Wed, 30 Jul 2025 10:42:01 +0200 Subject: [PATCH 18/34] Fixing two type issues --- manim/camera/camera.py | 2 +- manim/mobject/graphing/number_line.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/manim/camera/camera.py b/manim/camera/camera.py index 43c66a53e3..745b536bbc 100644 --- a/manim/camera/camera.py +++ b/manim/camera/camera.py @@ -205,7 +205,7 @@ def type_or_raise( from ..mobject.types.image_mobject import AbstractImageMobject self.display_funcs: dict[ - type[Mobject], Callable[[list[Mobject], PixelArray], None] + type[Mobject], Callable[[list[Mobject], PixelArray], Any] ] = { VMobject: self.display_multiple_vectorized_mobjects, PMobject: self.display_multiple_point_cloud_mobjects, diff --git a/manim/mobject/graphing/number_line.py b/manim/mobject/graphing/number_line.py index 647b9101b0..b6426e87bb 100644 --- a/manim/mobject/graphing/number_line.py +++ b/manim/mobject/graphing/number_line.py @@ -25,8 +25,9 @@ from manim.constants import * from manim.mobject.geometry.line import Line from manim.mobject.graphing.scale import LinearBase, _ScaleBase -from manim.mobject.text.numbers import DecimalNumber +from manim.mobject.text.numbers import DecimalNumber, Integer from manim.mobject.text.tex_mobject import MathTex, Tex +from manim.mobject.text.text_mobject import Text from manim.mobject.types.vectorized_mobject import VGroup, VMobject from manim.utils.bezier import interpolate from manim.utils.config_ops import merge_dicts_recursively @@ -615,6 +616,7 @@ def add_labels( label = self._create_label_tex(label, label_constructor) if hasattr(label, "font_size"): + assert isinstance(label, (MathTex, Tex, Text, Integer)), label label.font_size = font_size else: raise AttributeError(f"{label} is not compatible with add_labels.") From 9f148ea14af5b125a5234646e6035a2dcca1dbcb Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Wed, 30 Jul 2025 10:54:09 +0200 Subject: [PATCH 19/34] ... --- manim/mobject/graphing/probability.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/manim/mobject/graphing/probability.py b/manim/mobject/graphing/probability.py index d70f5fb88e..e787199133 100644 --- a/manim/mobject/graphing/probability.py +++ b/manim/mobject/graphing/probability.py @@ -14,11 +14,10 @@ from manim.constants import * from manim.mobject.geometry.polygram import Rectangle from manim.mobject.graphing.coordinate_systems import Axes -from manim.mobject.mobject import Mobject -from manim.mobject.opengl.opengl_mobject import OpenGLMobject +from manim.mobject.opengl.opengl_vectorized_mobject import OpenGLVMobject from manim.mobject.svg.brace import Brace from manim.mobject.text.tex_mobject import MathTex, Tex -from manim.mobject.types.vectorized_mobject import VGroup +from manim.mobject.types.vectorized_mobject import VGroup, VMobject from manim.typing import Vector3D from manim.utils.color import ( BLUE_E, @@ -151,7 +150,7 @@ def divide_vertically(self, *args: Any, **kwargs: Any) -> None: def get_subdivision_braces_and_labels( self, parts: VGroup, - labels: list[str | Mobject | OpenGLMobject], + labels: list[str | VMobject | OpenGLVMobject], direction: Vector3D, buff: float = SMALL_BUFF, min_num_quads: int = 1, @@ -160,7 +159,7 @@ def get_subdivision_braces_and_labels( braces = VGroup() for label, part in zip(labels, parts): brace = Brace(part, direction, min_num_quads=min_num_quads, buff=buff) - if isinstance(label, (Mobject, OpenGLMobject)): + if isinstance(label, (VMobject, OpenGLVMobject)): label_mob = label else: label_mob = MathTex(label) @@ -180,7 +179,7 @@ def get_subdivision_braces_and_labels( def get_side_braces_and_labels( self, - labels: list[str | Mobject | OpenGLMobject], + labels: list[str | VMobject | OpenGLVMobject], direction: Vector3D = LEFT, **kwargs: Any, ) -> VGroup: @@ -191,14 +190,14 @@ def get_side_braces_and_labels( ) def get_top_braces_and_labels( - self, labels: list[str | Mobject | OpenGLMobject], **kwargs: Any + self, labels: list[str | VMobject | OpenGLVMobject], **kwargs: Any ) -> VGroup: assert hasattr(self, "vertical_parts") parts = self.vertical_parts return self.get_subdivision_braces_and_labels(parts, labels, UP, **kwargs) def get_bottom_braces_and_labels( - self, labels: list[str | Mobject | OpenGLMobject], **kwargs: Any + self, labels: list[str | VMobject | OpenGLVMobject], **kwargs: Any ) -> VGroup: assert hasattr(self, "vertical_parts") parts = self.vertical_parts From efe556535947853b491dfec4f0f975468e1d4067 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Wed, 30 Jul 2025 21:14:21 +0200 Subject: [PATCH 20/34] Updates based on input from Chopan50 --- manim/camera/camera.py | 31 ++++++++++++++------------- manim/mobject/graphing/probability.py | 4 ---- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/manim/camera/camera.py b/manim/camera/camera.py index 745b536bbc..46b64a6014 100644 --- a/manim/camera/camera.py +++ b/manim/camera/camera.py @@ -18,7 +18,7 @@ from scipy.spatial.distance import pdist from typing_extensions import Self -from manim.typing import PixelArray, Point3D_Array +from manim.typing import MatrixMN, PixelArray, Point3D, Point3D_Array from .. import config, logger from ..constants import * @@ -76,13 +76,13 @@ class Camera: def __init__( self, background_image: str | None = None, - frame_center: np.ndarray = ORIGIN, + frame_center: Point3D = ORIGIN, image_mode: str = "RGBA", n_channels: int = 4, pixel_array_dtype: str = "uint8", cairo_line_width_multiple: float = 0.01, use_z_index: bool = True, - background: np.ndarray | None = None, + background: PixelArray | None = None, pixel_height: int | None = None, pixel_width: int | None = None, frame_height: float | None = None, @@ -100,6 +100,9 @@ def __init__( self.cairo_line_width_multiple = cairo_line_width_multiple self.use_z_index = use_z_index self.background = background + self.background_colored_vmobject_displayer: ( + BackgroundColoredVMobjectDisplayer | None + ) = None if pixel_height is None: pixel_height = config["pixel_height"] @@ -290,7 +293,7 @@ def init_background(self) -> None: def get_image( self, pixel_array: PixelArray | list | tuple | None = None - ) -> PixelArray: + ) -> Image.Image: """Returns an image from the passed pixel array, or from the current frame if the passed pixel array is none. @@ -302,7 +305,7 @@ def get_image( Returns ------- - PIL.Image + PIL.Image.Image The PIL image of the array. """ if pixel_array is None: @@ -550,7 +553,7 @@ def capture_mobjects(self, mobjects: Iterable[Mobject], **kwargs: Any) -> None: # NOTE: None of the methods below have been mentioned outside of their definitions. Their DocStrings are not as # detailed as possible. - def get_cached_cairo_context(self, pixel_array: PixelArray) -> cairo.Context: + def get_cached_cairo_context(self, pixel_array: PixelArray) -> cairo.Context | None: """Returns the cached cairo context of the passed pixel array if it exists, and None if it doesn't. @@ -603,7 +606,7 @@ def get_cairo_context(self, pixel_array: PixelArray) -> cairo.Context: fh = self.frame_height fc = self.frame_center surface = cairo.ImageSurface.create_for_data( - pixel_array, + pixel_array.data, cairo.FORMAT_ARGB32, pw, ph, @@ -704,8 +707,6 @@ def set_cairo_context_path(self, ctx: cairo.Context, vmobject: VMobject) -> Self # TODO, shouldn't this be handled in transform_points_pre_display? # points = points - self.get_frame_center() if len(points) == 0: - # TODO: - # Here the return value is modified. Is that ok? return self ctx.new_path() @@ -722,7 +723,7 @@ def set_cairo_context_path(self, ctx: cairo.Context, vmobject: VMobject) -> Self return self def set_cairo_context_color( - self, ctx: cairo.Context, rgbas: PixelArray, vmobject: VMobject + self, ctx: cairo.Context, rgbas: MatrixMN, vmobject: VMobject ) -> Self: """Sets the color of the cairo context @@ -863,11 +864,11 @@ def get_background_colored_vmobject_displayer( Object that displays VMobjects that have the same color as the background. """ - # Quite wordy to type out a bunch - bcvd = "background_colored_vmobject_displayer" - if not hasattr(self, bcvd): - setattr(self, bcvd, BackgroundColoredVMobjectDisplayer(self)) - return getattr(self, bcvd) # type: ignore[no-any-return] + if self.background_colored_vmobject_displayer is None: + self.background_colored_vmobject_displayer = ( + BackgroundColoredVMobjectDisplayer(self) + ) + return self.background_colored_vmobject_displayer def display_multiple_background_colored_vmobjects( self, cvmobjects: Iterable[VMobject], pixel_array: PixelArray diff --git a/manim/mobject/graphing/probability.py b/manim/mobject/graphing/probability.py index e787199133..ab65f85743 100644 --- a/manim/mobject/graphing/probability.py +++ b/manim/mobject/graphing/probability.py @@ -101,10 +101,6 @@ def get_division_along_dimension( colors: Sequence[ParsableManimColor], vect: Vector3D, ) -> VGroup: - # Consideration, can we somehow describe that a VGroup only - # contains objects of a certain kind? - # For this function I would like to describe the return type as - # VGroup[SampleSpace]. p_list_complete = self.complete_p_list(p_list) colors_in_gradient = color_gradient(colors, len(p_list)) From e613eb1e1b3c27d648098499f7fc073555bdfdaf Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Wed, 30 Jul 2025 21:25:24 +0200 Subject: [PATCH 21/34] Reverting to type ignores and assert statements to fix the last type errors. --- manim/camera/camera.py | 2 +- manim/camera/multi_camera.py | 8 ++++---- manim/mobject/graphing/probability.py | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/manim/camera/camera.py b/manim/camera/camera.py index 46b64a6014..dc30cc06c9 100644 --- a/manim/camera/camera.py +++ b/manim/camera/camera.py @@ -210,7 +210,7 @@ def type_or_raise( self.display_funcs: dict[ type[Mobject], Callable[[list[Mobject], PixelArray], Any] ] = { - VMobject: self.display_multiple_vectorized_mobjects, + VMobject: self.display_multiple_vectorized_mobjects, # type: ignore[dict-item] PMobject: self.display_multiple_point_cloud_mobjects, AbstractImageMobject: self.display_multiple_image_mobjects, Mobject: lambda batch, pa: batch, # Do nothing diff --git a/manim/camera/multi_camera.py b/manim/camera/multi_camera.py index 44da140db7..f4bd18a47c 100644 --- a/manim/camera/multi_camera.py +++ b/manim/camera/multi_camera.py @@ -65,10 +65,10 @@ def update_sub_cameras(self) -> None: """Reshape sub_camera pixel_arrays""" for imfc in self.image_mobjects_from_cameras: pixel_height, pixel_width = self.pixel_array.shape[:2] - imfc.camera.frame_shape = ( - imfc.camera.frame.height, - imfc.camera.frame.width, - ) + # imfc.camera.frame_shape = ( + # imfc.camera.frame.height, + # imfc.camera.frame.width, + # ) imfc.camera.reset_pixel_shape( int(pixel_height * imfc.height / self.frame_height), int(pixel_width * imfc.width / self.frame_width), diff --git a/manim/mobject/graphing/probability.py b/manim/mobject/graphing/probability.py index ab65f85743..1a241e51aa 100644 --- a/manim/mobject/graphing/probability.py +++ b/manim/mobject/graphing/probability.py @@ -104,7 +104,6 @@ def get_division_along_dimension( p_list_complete = self.complete_p_list(p_list) colors_in_gradient = color_gradient(colors, len(p_list)) - # TODO: Is this needed? assert isinstance(colors_in_gradient, list) last_point = self.get_edge_center(-vect) @@ -163,10 +162,11 @@ def get_subdivision_braces_and_labels( label_mob.next_to(brace, direction, buff) braces.add(brace) + assert isinstance(label_mob, VMobject) label_mobs.add(label_mob) - parts.braces = braces - parts.labels = label_mobs - parts.label_kwargs = { + parts.braces = braces # type: ignore[attr-defined] + parts.labels = label_mobs # type: ignore[attr-defined] + parts.label_kwargs = { # type: ignore[attr-defined] "labels": label_mobs.copy(), "direction": direction, "buff": buff, From c03c8d8346a5930de6bc463027c4729fd3f376c6 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Thu, 31 Jul 2025 22:17:20 +0200 Subject: [PATCH 22/34] Update manim/mobject/graphing/functions.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Francisco Manríquez Novoa <49853152+chopan050@users.noreply.github.com> --- manim/mobject/graphing/functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manim/mobject/graphing/functions.py b/manim/mobject/graphing/functions.py index 122d5cf8fb..beb5e5dcbe 100644 --- a/manim/mobject/graphing/functions.py +++ b/manim/mobject/graphing/functions.py @@ -217,12 +217,12 @@ def construct(self): def __init__( self, function: Callable[[float], Any], - x_range: np.array | None = None, + x_range: tuple[float, float] | tuple[float, float, float] | None = None, color: ParsableManimColor = YELLOW, **kwargs: Any, ) -> None: if x_range is None: - x_range = np.array([-config["frame_x_radius"], config["frame_x_radius"]]) + x_range = (-config["frame_x_radius"], config["frame_x_radius"]) self.x_range = x_range self.parametric_function: Callable[[float], np.array] = lambda t: np.array( From 0da8a6af48017caf4123ea0821451687f31a6107 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Thu, 31 Jul 2025 22:17:49 +0200 Subject: [PATCH 23/34] Update manim/mobject/graphing/functions.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Francisco Manríquez Novoa <49853152+chopan050@users.noreply.github.com> --- manim/mobject/graphing/functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manim/mobject/graphing/functions.py b/manim/mobject/graphing/functions.py index beb5e5dcbe..c2e7d65387 100644 --- a/manim/mobject/graphing/functions.py +++ b/manim/mobject/graphing/functions.py @@ -225,7 +225,7 @@ def __init__( x_range = (-config["frame_x_radius"], config["frame_x_radius"]) self.x_range = x_range - self.parametric_function: Callable[[float], np.array] = lambda t: np.array( + self.parametric_function: Callable[[float], Point3D] = lambda t: np.array( [t, function(t), 0] ) self.function: Callable[[float], Any] = function From 9a9e412f0f00ba7c80efc0f9e6d371a1128d4dab Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Thu, 31 Jul 2025 22:18:12 +0200 Subject: [PATCH 24/34] Update manim/mobject/graphing/functions.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Francisco Manríquez Novoa <49853152+chopan050@users.noreply.github.com> --- manim/mobject/graphing/functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manim/mobject/graphing/functions.py b/manim/mobject/graphing/functions.py index c2e7d65387..819a155539 100644 --- a/manim/mobject/graphing/functions.py +++ b/manim/mobject/graphing/functions.py @@ -234,7 +234,7 @@ def __init__( def get_function(self) -> Callable[[float], Any]: return self.function - def get_point_from_function(self, x: float) -> np.array: + def get_point_from_function(self, x: float) -> Point3D: return self.parametric_function(x) From efc7a615917eae5d7e01877494cf18661958fa60 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Thu, 31 Jul 2025 22:19:41 +0200 Subject: [PATCH 25/34] Update manim/mobject/graphing/number_line.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Francisco Manríquez Novoa <49853152+chopan050@users.noreply.github.com> --- manim/mobject/graphing/number_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manim/mobject/graphing/number_line.py b/manim/mobject/graphing/number_line.py index b6426e87bb..61026ba334 100644 --- a/manim/mobject/graphing/number_line.py +++ b/manim/mobject/graphing/number_line.py @@ -276,7 +276,7 @@ def __init__( ) def rotate_about_zero( - self, angle: float, axis: Sequence[float] = OUT, **kwargs: Any + self, angle: float, axis: Vector3D = OUT, **kwargs: Any ) -> Self: return self.rotate_about_number(0, angle, axis, **kwargs) From 14c339aa4e148217b4a164c31cccd7dde07f5cbc Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Thu, 31 Jul 2025 22:20:09 +0200 Subject: [PATCH 26/34] Update manim/mobject/graphing/number_line.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Francisco Manríquez Novoa <49853152+chopan050@users.noreply.github.com> --- manim/mobject/graphing/number_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manim/mobject/graphing/number_line.py b/manim/mobject/graphing/number_line.py index 61026ba334..905dcf65e9 100644 --- a/manim/mobject/graphing/number_line.py +++ b/manim/mobject/graphing/number_line.py @@ -281,7 +281,7 @@ def rotate_about_zero( return self.rotate_about_number(0, angle, axis, **kwargs) def rotate_about_number( - self, number: float, angle: float, axis: Sequence[float] = OUT, **kwargs: Any + self, number: float, angle: float, axis: Vector3D = OUT, **kwargs: Any ) -> Self: return self.rotate(angle, axis, about_point=self.n2p(number), **kwargs) From ff789bbb9c68c0bdf14d89971274607c4f06406b Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Thu, 31 Jul 2025 22:27:04 +0200 Subject: [PATCH 27/34] Update manim/mobject/graphing/number_line.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Francisco Manríquez Novoa <49853152+chopan050@users.noreply.github.com> --- manim/mobject/graphing/number_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manim/mobject/graphing/number_line.py b/manim/mobject/graphing/number_line.py index 905dcf65e9..0f1bb9eefa 100644 --- a/manim/mobject/graphing/number_line.py +++ b/manim/mobject/graphing/number_line.py @@ -430,7 +430,7 @@ def point_to_number(self, point: Sequence[float]) -> float: ) return interpolate(self.x_min, self.x_max, proportion) - def n2p(self, number: float | np.ndarray) -> np.ndarray: + def n2p(self, number: float | np.ndarray) -> Point3D: """Abbreviation for :meth:`~.NumberLine.number_to_point`.""" return self.number_to_point(number) From 1b1798d9d349139fbe9c93c204f45cb09f1a6f94 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Thu, 31 Jul 2025 22:27:36 +0200 Subject: [PATCH 28/34] Suggestions from Chopan50 --- manim/mobject/graphing/number_line.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/manim/mobject/graphing/number_line.py b/manim/mobject/graphing/number_line.py index 0f1bb9eefa..5c3719bb89 100644 --- a/manim/mobject/graphing/number_line.py +++ b/manim/mobject/graphing/number_line.py @@ -17,7 +17,7 @@ from typing_extensions import Self from manim.mobject.geometry.tips import ArrowTip - from manim.typing import Point3DLike + from manim.typing import Point3DLike, Point3D, Vector3D import numpy as np @@ -162,7 +162,7 @@ def __init__( # numbers/labels include_numbers: bool = False, font_size: float = 36, - label_direction: Sequence[float] = DOWN, + label_direction: Point3DLike = DOWN, label_constructor: type[MathTex] = MathTex, scaling: _ScaleBase = LinearBase(), line_to_number_buff: float = MED_SMALL_BUFF, @@ -442,7 +442,7 @@ def get_unit_size(self) -> float: val: float = self.get_length() / (self.x_range[1] - self.x_range[0]) return val - def get_unit_vector(self) -> np.ndarray: + def get_unit_vector(self) -> Vector3D: return super().get_unit_vector() * self.unit_size def get_number_mobject( @@ -569,7 +569,7 @@ def add_numbers( def add_labels( self, dict_values: dict[float, str | float | VMobject], - direction: Sequence[float] | None = None, + direction: Point3DLike | None = None, buff: float | None = None, font_size: float | None = None, label_constructor: type[MathTex] | None = None, @@ -667,7 +667,7 @@ def _decimal_places_from_step(step: float) -> int: return 0 return len(step_str.split(".")[-1]) - def __matmul__(self, other: float) -> np.ndarray: + def __matmul__(self, other: float) -> Point3D: return self.n2p(other) def __rmatmul__(self, other: Point3DLike | Mobject) -> float: From 9263abd69356a007094bf3517cf5217d5b5e099e Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Thu, 31 Jul 2025 22:34:40 +0200 Subject: [PATCH 29/34] More suggestions from Chopan50 --- manim/mobject/graphing/coordinate_systems.py | 4 ++-- manim/mobject/graphing/probability.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/manim/mobject/graphing/coordinate_systems.py b/manim/mobject/graphing/coordinate_systems.py index 24bb42e8b8..aa7fda904d 100644 --- a/manim/mobject/graphing/coordinate_systems.py +++ b/manim/mobject/graphing/coordinate_systems.py @@ -155,7 +155,7 @@ def __init__( self.num_sampled_graph_points_per_tick = 10 self.x_axis: NumberLine - def coords_to_point(self, *coords: ManimFloat) -> Point3DLike: + def coords_to_point(self, *coords: ManimFloat) -> Point3D: raise NotImplementedError() def point_to_coords(self, point: Point3DLike) -> list[ManimFloat]: @@ -233,7 +233,7 @@ def get_axis(self, index: int) -> NumberLine: val: NumberLine = self.get_axes()[index] return val - def get_origin(self) -> Point3DLike: + def get_origin(self) -> Point3D: """Gets the origin of :class:`~.Axes`. Returns diff --git a/manim/mobject/graphing/probability.py b/manim/mobject/graphing/probability.py index 1a241e51aa..9573e1c74e 100644 --- a/manim/mobject/graphing/probability.py +++ b/manim/mobject/graphing/probability.py @@ -87,7 +87,7 @@ def add_title( def add_label(self, label: str) -> None: self.label = label - def complete_p_list(self, p_list: list[tuple]) -> list[float]: + def complete_p_list(self, p_list: float | Iterable[float]) -> list[float]: new_p_list = list(tuplify(p_list)) remainder = 1.0 - sum(new_p_list) if abs(remainder) > EPSILON: @@ -96,7 +96,7 @@ def complete_p_list(self, p_list: list[tuple]) -> list[float]: def get_division_along_dimension( self, - p_list: list[tuple], + p_list: float | Iterable[float], dim: int, colors: Sequence[ParsableManimColor], vect: Vector3D, @@ -120,7 +120,7 @@ def get_division_along_dimension( def get_horizontal_division( self, - p_list: list[tuple], + p_list: float | Iterable[float], colors: Sequence[ParsableManimColor] = [GREEN_E, BLUE_E], vect: Vector3D = DOWN, ) -> VGroup: @@ -128,7 +128,7 @@ def get_horizontal_division( def get_vertical_division( self, - p_list: list[tuple], + p_list: float | Iterable[float], colors: Sequence[ParsableManimColor] = [MAROON_B, YELLOW], vect: Vector3D = RIGHT, ) -> VGroup: From 8309fe99e4388e1f9dcd98625591c86e99cae102 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Thu, 31 Jul 2025 22:43:32 +0200 Subject: [PATCH 30/34] Updates --- manim/mobject/graphing/number_line.py | 2 +- manim/mobject/graphing/probability.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/manim/mobject/graphing/number_line.py b/manim/mobject/graphing/number_line.py index 5c3719bb89..b0a038f781 100644 --- a/manim/mobject/graphing/number_line.py +++ b/manim/mobject/graphing/number_line.py @@ -17,7 +17,7 @@ from typing_extensions import Self from manim.mobject.geometry.tips import ArrowTip - from manim.typing import Point3DLike, Point3D, Vector3D + from manim.typing import Point3D, Point3DLike, Vector3D import numpy as np diff --git a/manim/mobject/graphing/probability.py b/manim/mobject/graphing/probability.py index 9573e1c74e..309e0b7ec2 100644 --- a/manim/mobject/graphing/probability.py +++ b/manim/mobject/graphing/probability.py @@ -62,7 +62,7 @@ def __init__( stroke_width: float = 0.5, stroke_color: ParsableManimColor = LIGHT_GREY, default_label_scale_val: float = 1, - ) -> None: + ): super().__init__( height=height, width=width, @@ -88,7 +88,8 @@ def add_label(self, label: str) -> None: self.label = label def complete_p_list(self, p_list: float | Iterable[float]) -> list[float]: - new_p_list = list(tuplify(p_list)) + p_list_tuplified: tuple[float] = tuplify(p_list) + new_p_list = list(p_list_tuplified) remainder = 1.0 - sum(new_p_list) if abs(remainder) > EPSILON: new_p_list.append(remainder) @@ -102,7 +103,7 @@ def get_division_along_dimension( vect: Vector3D, ) -> VGroup: p_list_complete = self.complete_p_list(p_list) - colors_in_gradient = color_gradient(colors, len(p_list)) + colors_in_gradient = color_gradient(colors, len(p_list_complete)) assert isinstance(colors_in_gradient, list) @@ -287,7 +288,7 @@ def __init__( bar_fill_opacity: float = 0.7, bar_stroke_width: float = 3, **kwargs: Any, - ) -> None: + ): if isinstance(bar_colors, str): logger.warning( "Passing a string to `bar_colors` has been deprecated since v0.15.2 and will be removed after v0.17.0, the parameter must be a list. " From 1c582b7dd112a6950b279c7683feeb1ada4953e5 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Thu, 31 Jul 2025 23:12:36 +0200 Subject: [PATCH 31/34] Minor improvements to coordinate_systems.py --- manim/mobject/graphing/coordinate_systems.py | 50 +++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/manim/mobject/graphing/coordinate_systems.py b/manim/mobject/graphing/coordinate_systems.py index aa7fda904d..811581f8fb 100644 --- a/manim/mobject/graphing/coordinate_systems.py +++ b/manim/mobject/graphing/coordinate_systems.py @@ -126,7 +126,7 @@ def __init__( x_length: float | None = None, y_length: float | None = None, dimension: int = 2, - ) -> None: + ): self.dimension = dimension default_step = 1 @@ -156,6 +156,8 @@ def __init__( self.x_axis: NumberLine def coords_to_point(self, *coords: ManimFloat) -> Point3D: + # TODO: I think the method should be able to return more than just a single point. + # E.g. see the implementation of it on line 2065. raise NotImplementedError() def point_to_coords(self, point: Point3DLike) -> list[ManimFloat]: @@ -202,7 +204,7 @@ def point_to_polar(self, point: Point2DLike) -> Point2D: Returns ------- - Tuple[:class:`float`, :class:`float`] + Point2D The coordinate radius (:math:`r`) and the coordinate azimuth (:math:`\theta`). """ x, y = self.point_to_coords(point) @@ -261,8 +263,8 @@ def get_y_unit_size(self) -> float: def get_x_axis_label( self, label: float | str | VMobject, - edge: Sequence[float] = UR, - direction: Sequence[float] = UR, + edge: Vector3D = UR, + direction: Vector3D = UR, buff: float = SMALL_BUFF, **kwargs: Any, ) -> Mobject: @@ -304,8 +306,8 @@ def construct(self): def get_y_axis_label( self, label: float | str | VMobject, - edge: Sequence[float] = UR, - direction: Sequence[float] = UP * 0.5 + RIGHT, + edge: Vector3D = UR, + direction: Vector3D = UP * 0.5 + RIGHT, buff: float = SMALL_BUFF, **kwargs: Any, ) -> Mobject: @@ -351,8 +353,8 @@ def _get_axis_label( self, label: float | str | VMobject, axis: Mobject, - edge: Sequence[float], - direction: Sequence[float], + edge: Vector3D, + direction: Vector3D, buff: float = SMALL_BUFF, ) -> Mobject: """Gets the label for an axis. @@ -457,7 +459,7 @@ def add_coordinates( def get_line_from_axis_to_point( self, index: int, - point: Sequence[float], + point: Point3DLike, line_config: dict | None = ..., color: ParsableManimColor | None = ..., stroke_width: float = ..., @@ -467,7 +469,7 @@ def get_line_from_axis_to_point( def get_line_from_axis_to_point( self, index: int, - point: Sequence[float], + point: Point3DLike, line_func: type[LineType], line_config: dict | None = ..., color: ParsableManimColor | None = ..., @@ -522,7 +524,7 @@ def get_line_from_axis_to_point( # type: ignore[no-untyped-def] line = line_func(axis.get_projection(point), point, **line_config) return line - def get_vertical_line(self, point: Sequence[float], **kwargs: Any) -> Line: + def get_vertical_line(self, point: Point3DLike, **kwargs: Any) -> Line: """A vertical line from the x-axis to a given point in the scene. Parameters @@ -556,7 +558,7 @@ def construct(self): """ return self.get_line_from_axis_to_point(0, point, **kwargs) - def get_horizontal_line(self, point: Sequence[float], **kwargs: Any) -> Line: + def get_horizontal_line(self, point: Point3DLike, **kwargs: Any) -> Line: """A horizontal line from the y-axis to a given point in the scene. Parameters @@ -588,7 +590,7 @@ def construct(self): """ return self.get_line_from_axis_to_point(1, point, **kwargs) - def get_lines_to_point(self, point: Sequence[float], **kwargs: Any) -> VGroup: + def get_lines_to_point(self, point: Point3DLike, **kwargs: Any) -> VGroup: """Generate both horizontal and vertical lines from the axis to a point. Parameters @@ -634,7 +636,9 @@ def plot( function: Callable[[float], float], x_range: Sequence[float] | None = None, use_vectorized: bool = False, - colorscale: Union[Iterable[Color], Iterable[Color, float]] | None = None, + colorscale: Iterable[ParsableManimColor] + | Iterable[ParsableManimColor, float] + | None = None, colorscale_axis: int = 1, **kwargs: Any, ) -> ParametricFunction: @@ -1595,7 +1599,7 @@ def antideriv(x): x_vals = np.linspace(0, x, samples, axis=1 if use_vectorized else 0) f_vec = np.vectorize(graph.underlying_function) y_vals = f_vec(x_vals) - return np.trapz(y_vals, x_vals) + y_intercept + return np.trapezoid(y_vals, x_vals) + y_intercept return self.plot(antideriv, use_vectorized=use_vectorized, **kwargs) @@ -1929,7 +1933,7 @@ def __init__( y_axis_config: dict | None = None, tips: bool = True, **kwargs: Any, - ) -> None: + ): VGroup.__init__(self, **kwargs) CoordinateSystem.__init__(self, x_range, y_range, x_length, y_length) @@ -2430,12 +2434,12 @@ def __init__( z_axis_config: dict[str, Any] | None = None, z_normal: Vector3D = DOWN, num_axis_pieces: int = 20, - light_source: Sequence[float] = 9 * DOWN + 7 * LEFT + 10 * OUT, + light_source: Point3DLike = 9 * DOWN + 7 * LEFT + 10 * OUT, # opengl stuff (?) depth: Any = None, gloss: float = 0.5, **kwargs: dict[str, Any], - ) -> None: + ): super().__init__( x_range=x_range, x_length=x_length, @@ -2457,7 +2461,7 @@ def __init__( self.z_normal = z_normal self.num_axis_pieces = num_axis_pieces - self.light_source = light_source + self.light_source = np.array(light_source) self.dimension = 3 @@ -2515,8 +2519,8 @@ def make_func(axis): def get_y_axis_label( self, label: float | str | VMobject, - edge: Sequence[float] = UR, - direction: Sequence[float] = UR, + edge: Vector3D = UR, + direction: Vector3D = UR, buff: float = SMALL_BUFF, rotation: float = PI / 2, rotation_axis: Vector3D = OUT, @@ -3023,7 +3027,7 @@ def __init__( faded_line_ratio: int = 1, make_smooth_after_applying_functions: bool = True, **kwargs: Any, - ) -> None: + ): # error catching if azimuth_units in ["PI radians", "TAU radians", "degrees", "gradians", None]: self.azimuth_units = azimuth_units @@ -3377,7 +3381,7 @@ def construct(self): """ - def __init__(self, **kwargs: Any) -> None: + def __init__(self, **kwargs: Any): super().__init__( **kwargs, ) From 603779c524b3b57e9979df71826f56c7c32dae9b Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Thu, 31 Jul 2025 23:16:29 +0200 Subject: [PATCH 32/34] Minor improvements to number_line.py --- manim/mobject/graphing/number_line.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/manim/mobject/graphing/number_line.py b/manim/mobject/graphing/number_line.py index b0a038f781..14964bffc3 100644 --- a/manim/mobject/graphing/number_line.py +++ b/manim/mobject/graphing/number_line.py @@ -170,7 +170,7 @@ def __init__( numbers_to_exclude: Iterable[float] | None = None, numbers_to_include: Iterable[float] | None = None, **kwargs: Any, - ) -> None: + ): # avoid mutable arguments in defaults if numbers_to_exclude is None: numbers_to_exclude = [] @@ -448,7 +448,7 @@ def get_unit_vector(self) -> Vector3D: def get_number_mobject( self, x: float, - direction: Sequence[float] | None = None, + direction: Vector3D | None = None, buff: float | None = None, font_size: float | None = None, label_constructor: type[MathTex] | None = None, @@ -683,7 +683,7 @@ def __init__( numbers_with_elongated_ticks: list[float] | None = None, decimal_number_config: dict[str, Any] | None = None, **kwargs: Any, - ) -> None: + ): numbers_with_elongated_ticks = ( [0, 1] if numbers_with_elongated_ticks is None From 3307494e9552ebf434767d01c4aa586eb3db868a Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Thu, 31 Jul 2025 23:17:49 +0200 Subject: [PATCH 33/34] Minor improvements to functions.py --- manim/mobject/graphing/functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manim/mobject/graphing/functions.py b/manim/mobject/graphing/functions.py index 819a155539..d125f45b6b 100644 --- a/manim/mobject/graphing/functions.py +++ b/manim/mobject/graphing/functions.py @@ -248,7 +248,7 @@ def __init__( max_quads: int = 1500, use_smoothing: bool = True, **kwargs: Any, - ) -> None: + ): """An implicit function. Parameters From 55dac2fa01b8293d80691fb349303c33227e96a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Manr=C3=ADquez=20Novoa?= <49853152+chopan050@users.noreply.github.com> Date: Thu, 31 Jul 2025 20:47:17 -0400 Subject: [PATCH 34/34] Update manim/mobject/graphing/scale.py --- manim/mobject/graphing/scale.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manim/mobject/graphing/scale.py b/manim/mobject/graphing/scale.py index d894a320eb..b6ed2b4ce3 100644 --- a/manim/mobject/graphing/scale.py +++ b/manim/mobject/graphing/scale.py @@ -32,7 +32,7 @@ def __init__(self, custom_labels: bool = False): def function(self, value: float) -> float: ... @overload - def function(self, value: np.array) -> np.array: ... + def function(self, value: np.ndarray) -> np.ndarray: ... def function(self, value: float) -> float: """The function that will be used to scale the values.