diff --git a/manim/animation/transform.py b/manim/animation/transform.py index a7c62a9e6a..7f16c668f5 100644 --- a/manim/animation/transform.py +++ b/manim/animation/transform.py @@ -49,6 +49,8 @@ from ..utils.rate_functions import smooth, squish_rate_func if TYPE_CHECKING: + from typing import Any + from ..scene.scene import Scene @@ -516,7 +518,7 @@ def construct(self): def __init__( self, - function: types.MethodType, + function: Callable, mobject: Mobject, run_time: float = DEFAULT_POINTWISE_FUNCTION_RUN_TIME, **kwargs, @@ -615,7 +617,9 @@ def __init__(self, mobject: Mobject, **kwargs) -> None: class ApplyFunction(Transform): - def __init__(self, function: types.MethodType, mobject: Mobject, **kwargs) -> None: + def __init__( + self, function: Callable[[Any], Any], mobject: Mobject, **kwargs: Any + ) -> None: self.function = function super().__init__(mobject, **kwargs) diff --git a/manim/animation/transform_matching_parts.py b/manim/animation/transform_matching_parts.py index dbf5dd294e..44396ea42c 100644 --- a/manim/animation/transform_matching_parts.py +++ b/manim/animation/transform_matching_parts.py @@ -20,6 +20,8 @@ from .transform import FadeTransformPieces, Transform if TYPE_CHECKING: + from typing import Any + from ..scene.scene import Scene @@ -74,7 +76,7 @@ def __init__( transform_mismatches: bool = False, fade_transform_mismatches: bool = False, key_map: dict | None = None, - **kwargs, + **kwargs: Any, ): if isinstance(mobject, OpenGLVMobject): group_type = OpenGLVGroup @@ -162,11 +164,11 @@ def clean_up_from_scene(self, scene: Scene) -> None: scene.add(self.to_add) @staticmethod - def get_mobject_parts(mobject: Mobject): + def get_mobject_parts(mobject: Mobject) -> None: raise NotImplementedError("To be implemented in subclass.") @staticmethod - def get_mobject_key(mobject: Mobject): + def get_mobject_key(mobject: Mobject) -> None: raise NotImplementedError("To be implemented in subclass.") @@ -206,7 +208,7 @@ def __init__( transform_mismatches: bool = False, fade_transform_mismatches: bool = False, key_map: dict | None = None, - **kwargs, + **kwargs: Any, ): super().__init__( mobject, @@ -269,7 +271,7 @@ def __init__( transform_mismatches: bool = False, fade_transform_mismatches: bool = False, key_map: dict | None = None, - **kwargs, + **kwargs: Any, ): super().__init__( mobject, diff --git a/manim/camera/camera.py b/manim/camera/camera.py index af5899c5c5..329f420c36 100644 --- a/manim/camera/camera.py +++ b/manim/camera/camera.py @@ -274,7 +274,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: np.ndarray | list | tuple | None = None + ) -> Image.Image: """Returns an image from the passed pixel array, or from the current frame if the passed pixel array is none. diff --git a/manim/camera/moving_camera.py b/manim/camera/moving_camera.py index 1d01d01e22..a1b7091484 100644 --- a/manim/camera/moving_camera.py +++ b/manim/camera/moving_camera.py @@ -10,6 +10,8 @@ __all__ = ["MovingCamera"] +from typing import TYPE_CHECKING + import numpy as np from .. import config @@ -19,6 +21,11 @@ from ..mobject.mobject import Mobject from ..utils.color import WHITE +if TYPE_CHECKING: + from typing import Any + + from manim.utils.color import ParsableManimColor + class MovingCamera(Camera): """ @@ -32,12 +39,12 @@ class MovingCamera(Camera): def __init__( self, - frame=None, - fixed_dimension=0, # width - default_frame_stroke_color=WHITE, - default_frame_stroke_width=0, - **kwargs, - ): + frame: ScreenRectangle | None = None, + fixed_dimension: Any = 0, # width + default_frame_stroke_color: ParsableManimColor = WHITE, + default_frame_stroke_width: float = 0, + **kwargs: Any, + ) -> None: """ Frame is a Mobject, (should almost certainly be a rectangle) determining which region of space the camera displays @@ -158,7 +165,7 @@ def cache_cairo_context(self, pixel_array, ctx): # self.frame_shape = (self.frame.height, width) # self.resize_frame_shape(fixed_dimension=self.fixed_dimension) - def get_mobjects_indicating_movement(self): + def get_mobjects_indicating_movement(self) -> list[ScreenRectangle]: """ Returns all mobjects whose movement implies that the camera should think of all other mobjects on the screen as moving diff --git a/manim/camera/multi_camera.py b/manim/camera/multi_camera.py index a5202135e9..46c8889c5b 100644 --- a/manim/camera/multi_camera.py +++ b/manim/camera/multi_camera.py @@ -5,7 +5,7 @@ __all__ = ["MultiCamera"] -from manim.mobject.types.image_mobject import ImageMobject +from manim.mobject.types.image_mobject import ImageMobject, ImageMobjectFromCamera from ..camera.moving_camera import MovingCamera from ..utils.iterables import list_difference_update @@ -38,7 +38,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 + ): """Adds an ImageMobject that's been obtained from the camera into the list ``self.image_mobject_from_cameras`` diff --git a/manim/camera/three_d_camera.py b/manim/camera/three_d_camera.py index f45854e810..6d4dd0bb9c 100644 --- a/manim/camera/three_d_camera.py +++ b/manim/camera/three_d_camera.py @@ -84,7 +84,7 @@ def capture_mobjects(self, mobjects, **kwargs): self.reset_rotation_matrix() super().capture_mobjects(mobjects, **kwargs) - def get_value_trackers(self): + def get_value_trackers(self) -> list[ValueTracker]: """A list of :class:`ValueTrackers <.ValueTracker>` of phi, theta, focal_distance, gamma and zoom. diff --git a/manim/gui/gui.py b/manim/gui/gui.py index 75ec67312c..d9cb91b3cf 100644 --- a/manim/gui/gui.py +++ b/manim/gui/gui.py @@ -11,6 +11,8 @@ from .. import __version__, config +from ..renderer.cairo_renderer import CairoRenderer +from ..renderer.opengl_renderer import OpenGLRenderer from ..utils.module_ops import scene_classes_from_file __all__ = ["configure_pygui"] @@ -20,7 +22,9 @@ window = dpg.generate_uuid() -def configure_pygui(renderer, widgets, update=True): +def configure_pygui( + renderer: CairoRenderer | OpenGLRenderer, widgets, update: bool = True +) -> None: if not dearpygui_imported: raise RuntimeError("Attempted to use DearPyGUI when it isn't imported.") if update: diff --git a/manim/mobject/geometry/arc.py b/manim/mobject/geometry/arc.py index c211deae01..abf03c9c7b 100644 --- a/manim/mobject/geometry/arc.py +++ b/manim/mobject/geometry/arc.py @@ -273,22 +273,22 @@ def get_first_handle(self) -> InternalPoint3D: # Type inference of extracting an element from a list, is not # supported by numpy, see this numpy issue # https://github.com/numpy/numpy/issues/16544 - return self.points[1] + return self.points[1] # type: ignore[no-any-return] def get_last_handle(self) -> InternalPoint3D: - return self.points[-2] + return self.points[-2] # type: ignore[no-any-return] def get_end(self) -> InternalPoint3D: if self.has_tip(): - return self.tip.get_start() + return self.tip.get_start() # type: ignore[return-value] else: - return super().get_end() + return super().get_end() # type: ignore[return-value] def get_start(self) -> InternalPoint3D: if self.has_start_tip(): - return self.start_tip.get_start() + return self.start_tip.get_start() # type: ignore[return-value] else: - return super().get_start() + return super().get_start() # type: ignore[return-value] def get_length(self) -> float: start, end = self.get_start_and_end() @@ -322,10 +322,10 @@ def __init__( if radius is None: # apparently None is passed by ArcBetweenPoints radius = 1.0 self.radius = radius - self.num_components = num_components - self.arc_center = arc_center - self.start_angle = start_angle - self.angle = angle + self.num_components: int = num_components + self.arc_center: InternalPoint3D = arc_center + self.start_angle: float = start_angle + self.angle: float = angle self._failed_to_get_center: bool = False super().__init__(**kwargs) @@ -484,7 +484,7 @@ def __init__( if radius is None: center = self.get_arc_center(warning=False) if not self._failed_to_get_center: - temp_radius: float = np.linalg.norm(np.array(start) - np.array(center)) + temp_radius: float = np.linalg.norm(np.array(start) - np.array(center)) # type: ignore[assignment] self.radius = temp_radius else: self.radius = np.inf @@ -661,7 +661,7 @@ def construct(self): perpendicular_bisector([np.asarray(p1), np.asarray(p2)]), perpendicular_bisector([np.asarray(p2), np.asarray(p3)]), ) - radius: float = np.linalg.norm(p1 - center) + radius: float = np.linalg.norm(p1 - center) # type: ignore[assignment] return Circle(radius=radius, **kwargs).shift(center) diff --git a/manim/mobject/geometry/boolean_ops.py b/manim/mobject/geometry/boolean_ops.py index a34d6fc7c4..dfd5b04654 100644 --- a/manim/mobject/geometry/boolean_ops.py +++ b/manim/mobject/geometry/boolean_ops.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING import numpy as np -from pathops import Path as SkiaPath +from pathops import Path as SkiaPath # type: ignore[import-untyped] from pathops import PathVerb, difference, intersection, union, xor from manim import config @@ -106,7 +106,7 @@ def _convert_vmobject_to_skia_path(self, vmobject: VMobject) -> SkiaPath: for _p0, p1, p2, p3 in quads: path.cubicTo(*p1[:2], *p2[:2], *p3[:2]) - if vmobject.consider_points_equals_2d(subpath[0], subpath[-1]): + if vmobject.consider_points_equals_2d(subpath[0], subpath[-1]): # type: ignore[arg-type] path.close() return path diff --git a/manim/mobject/geometry/line.py b/manim/mobject/geometry/line.py index ef0e4aa1ff..eb83ff253c 100644 --- a/manim/mobject/geometry/line.py +++ b/manim/mobject/geometry/line.py @@ -145,9 +145,9 @@ def _pointify( if isinstance(mob_or_point, (Mobject, OpenGLMobject)): mob = mob_or_point if direction is None: - return mob.get_center() + return mob.get_center() # type: ignore[return-value] else: - return mob.get_boundary_point(direction) + return mob.get_boundary_point(direction) # type: ignore[return-value] return np.array(mob_or_point) def set_path_arc(self, new_value: float) -> None: @@ -156,8 +156,8 @@ def set_path_arc(self, new_value: float) -> None: def put_start_and_end_on( self, - start: InternalPoint3D, - end: InternalPoint3D, + start: InternalPoint3D, # type: ignore[override] + end: InternalPoint3D, # type: ignore[override] ) -> Self: """Sets starts and end coordinates of a line. @@ -309,7 +309,7 @@ def get_start(self) -> InternalPoint3D: array([-1., 0., 0.]) """ if len(self.submobjects) > 0: - return self.submobjects[0].get_start() + return self.submobjects[0].get_start() # type: ignore[return-value] else: return super().get_start() @@ -324,7 +324,7 @@ def get_end(self) -> InternalPoint3D: array([1., 0., 0.]) """ if len(self.submobjects) > 0: - return self.submobjects[-1].get_end() + return self.submobjects[-1].get_end() # type: ignore[return-value] else: return super().get_end() diff --git a/manim/mobject/geometry/polygram.py b/manim/mobject/geometry/polygram.py index bfb9f00ab7..f2e0c5cdd8 100644 --- a/manim/mobject/geometry/polygram.py +++ b/manim/mobject/geometry/polygram.py @@ -89,7 +89,7 @@ def __init__( for vertices in vertex_groups: # The inferred type for *vertices is Any, but it should be # InternalPoint3D_Array - first_vertex, *vertices = vertices + first_vertex, *vertices = vertices # type: ignore[assignment] first_vertex = np.array(first_vertex) self.start_new_path(first_vertex) @@ -322,7 +322,7 @@ def construct(self): """ def __init__(self, *vertices: InternalPoint3D, **kwargs: Any) -> None: - super().__init__(vertices, **kwargs) + super().__init__(vertices, **kwargs) # type: ignore[arg-type] class RegularPolygram(Polygram): @@ -413,7 +413,7 @@ def gen_polygon_vertices(start_angle: float | None) -> tuple[list[Any], float]: vertex_groups.append(group) - super().__init__(*vertex_groups, **kwargs) + super().__init__(*vertex_groups, **kwargs) # type: ignore[arg-type] class RegularPolygon(RegularPolygram): diff --git a/manim/mobject/geometry/tips.py b/manim/mobject/geometry/tips.py index e137016a88..6c54a1da64 100644 --- a/manim/mobject/geometry/tips.py +++ b/manim/mobject/geometry/tips.py @@ -152,7 +152,7 @@ def tip_point(self) -> InternalPoint3D: # Type inference of extracting an element from a list, is not # supported by numpy, see this numpy issue # https://github.com/numpy/numpy/issues/16544 - return self.points[0] + return self.points[0] # type: ignore[no-any-return] @property def vector(self) -> Vector3D: diff --git a/manim/mobject/matrix.py b/manim/mobject/matrix.py index e506bc4528..490818c28a 100644 --- a/manim/mobject/matrix.py +++ b/manim/mobject/matrix.py @@ -400,7 +400,7 @@ def add_background_to_entries(self): mob.add_background_rectangle() return self - def get_mob_matrix(self): + def get_mob_matrix(self) -> VGroup: """Return the underlying mob matrix mobjects. Returns @@ -410,7 +410,7 @@ def get_mob_matrix(self): """ return self.mob_matrix - def get_entries(self): + def get_entries(self) -> VGroup: """Return the individual entries of the matrix. Returns @@ -435,7 +435,7 @@ def construct(self): """ return self.elements - def get_brackets(self): + def get_brackets(self) -> VGroup: r"""Return the bracket mobjects. Returns diff --git a/manim/mobject/text/tex_mobject.py b/manim/mobject/text/tex_mobject.py index 26334a60d9..22a4811960 100644 --- a/manim/mobject/text/tex_mobject.py +++ b/manim/mobject/text/tex_mobject.py @@ -29,6 +29,7 @@ from collections.abc import Iterable from functools import reduce from textwrap import dedent +from typing import TYPE_CHECKING from manim import config, logger from manim.constants import * @@ -38,6 +39,9 @@ from manim.utils.tex import TexTemplate from manim.utils.tex_file_writing import tex_to_svg_file +if TYPE_CHECKING: + from typing_extensions import Any + tex_string_to_mob_map = {} @@ -206,7 +210,7 @@ def _organize_submobjects_left_to_right(self): self.sort(lambda p: p[0]) return self - def get_tex_string(self): + def get_tex_string(self) -> str: return self.tex_string def init_colors(self, propagate_colors=True): @@ -255,13 +259,13 @@ def construct(self): def __init__( self, - *tex_strings, + *tex_strings: str, arg_separator: str = " ", substrings_to_isolate: Iterable[str] | None = None, tex_to_color_map: dict[str, ManimColor] = None, tex_environment: str = "align*", - **kwargs, - ): + **kwargs: Any, + ) -> None: self.tex_template = kwargs.pop("tex_template", config["tex_template"]) self.arg_separator = arg_separator self.substrings_to_isolate = ( @@ -447,8 +451,12 @@ class Tex(MathTex): """ def __init__( - self, *tex_strings, arg_separator="", tex_environment="center", **kwargs - ): + self, + *tex_strings: str, + arg_separator: str = "", + tex_environment: str = "center", + **kwargs: Any, + ) -> None: super().__init__( *tex_strings, arg_separator=arg_separator, diff --git a/manim/mobject/value_tracker.py b/manim/mobject/value_tracker.py index 9d81035e89..2d65e903d6 100644 --- a/manim/mobject/value_tracker.py +++ b/manim/mobject/value_tracker.py @@ -5,12 +5,17 @@ __all__ = ["ValueTracker", "ComplexValueTracker"] +from typing import TYPE_CHECKING + import numpy as np from manim.mobject.mobject import Mobject from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL from manim.utils.paths import straight_path +if TYPE_CHECKING: + from typing_extensions import Any, Self + class ValueTracker(Mobject, metaclass=ConvertToOpenGL): """A mobject that can be used for tracking (real-valued) parameters. @@ -69,7 +74,7 @@ def construct(self): """ - def __init__(self, value=0, **kwargs): + def __init__(self, value: float = 0, **kwargs: Any) -> None: super().__init__(**kwargs) self.set(points=np.zeros((1, 3))) self.set_value(value) @@ -78,7 +83,7 @@ def get_value(self) -> float: """Get the current value of this ValueTracker.""" return self.points[0, 0] - def set_value(self, value: float): + def set_value(self, value: float) -> Self: """Sets a new scalar value to the ValueTracker""" self.points[0, 0] = value return self diff --git a/manim/renderer/cairo_renderer.py b/manim/renderer/cairo_renderer.py index b97fa50299..4d6b52b644 100644 --- a/manim/renderer/cairo_renderer.py +++ b/manim/renderer/cairo_renderer.py @@ -18,6 +18,8 @@ from collections.abc import Iterable from typing import Any + import numpy.typing as npt + from manim.animation.animation import Animation from manim.scene.scene import Scene @@ -33,11 +35,11 @@ class CairoRenderer: def __init__( self, - file_writer_class=SceneFileWriter, - camera_class=None, - skip_animations=False, - **kwargs, - ): + file_writer_class: type[SceneFileWriter] = SceneFileWriter, + camera_class: Camera | None = None, + skip_animations: bool = False, + **kwargs: Any, + ) -> None: # All of the following are set to EITHER the value passed via kwargs, # OR the value stored in the global config dict at the time of # _instance construction_. @@ -51,7 +53,7 @@ def __init__( self.time = 0 self.static_image = None - def init_scene(self, scene): + def init_scene(self, scene: Scene) -> None: self.file_writer: Any = self._file_writer_class( self, scene.__class__.__name__, @@ -119,12 +121,12 @@ def play( def update_frame( # TODO Description in Docstring self, - scene, + scene: Scene, mobjects: typing.Iterable[Mobject] | None = None, include_submobjects: bool = True, ignore_skipping: bool = True, - **kwargs, - ): + **kwargs: Any, + ) -> None: """Update the frame. Parameters @@ -160,7 +162,7 @@ def render(self, scene, time, moving_mobjects): self.update_frame(scene, moving_mobjects) self.add_frame(self.get_frame()) - def get_frame(self): + def get_frame(self) -> npt.NDArray: """ Gets the current frame as NumPy array. @@ -263,7 +265,7 @@ def update_skipping_status(self): self.skip_animations = True raise EndSceneEarlyException() - def scene_finished(self, scene): + def scene_finished(self, scene: Scene) -> None: # If no animations in scene, render an image instead if self.num_plays: self.file_writer.finish() diff --git a/manim/renderer/opengl_renderer.py b/manim/renderer/opengl_renderer.py index cdb63ceaa5..e1a0f0fba7 100644 --- a/manim/renderer/opengl_renderer.py +++ b/manim/renderer/opengl_renderer.py @@ -4,7 +4,7 @@ import itertools as it import time from functools import cached_property -from typing import Any +from typing import TYPE_CHECKING, Any import moderngl import numpy as np @@ -35,6 +35,14 @@ render_opengl_vectorized_mobject_stroke, ) +if TYPE_CHECKING: + import numpy.typing as npt + from typing_extensions import Self + + from manim.mobject.mobject import Mobject + from manim.scene.scene import Scene + + __all__ = ["OpenGLCamera", "OpenGLRenderer"] @@ -102,7 +110,7 @@ def __init__( self.euler_angles = euler_angles self.refresh_rotation_matrix() - def get_position(self): + def get_position(self) -> npt.NDArray: return self.model_matrix[:, 3][:3] def set_position(self, position): @@ -123,7 +131,7 @@ def init_points(self): self.set_height(self.frame_shape[1], stretch=True) self.move_to(self.center_point) - def to_default_state(self): + def to_default_state(self) -> Self: self.center() self.set_height(config["frame_height"]) self.set_width(config["frame_width"]) @@ -166,28 +174,28 @@ def set_euler_angles(self, theta=None, phi=None, gamma=None): self.refresh_rotation_matrix() return self - def set_theta(self, theta): + def set_theta(self, theta: float) -> Self: return self.set_euler_angles(theta=theta) - def set_phi(self, phi): + def set_phi(self, phi: float) -> Self: return self.set_euler_angles(phi=phi) - def set_gamma(self, gamma): + def set_gamma(self, gamma: float) -> Self: return self.set_euler_angles(gamma=gamma) - def increment_theta(self, dtheta): + def increment_theta(self, dtheta: float) -> Self: self.euler_angles[0] += dtheta self.refresh_rotation_matrix() return self - def increment_phi(self, dphi): + def increment_phi(self, dphi: float) -> Self: phi = self.euler_angles[1] new_phi = clip(phi + dphi, -PI / 2, PI / 2) self.euler_angles[1] = new_phi self.refresh_rotation_matrix() return self - def increment_gamma(self, dgamma): + def increment_gamma(self, dgamma: float) -> Self: self.euler_angles[2] += dgamma self.refresh_rotation_matrix() return self @@ -199,15 +207,15 @@ def get_center(self): # Assumes first point is at the center return self.points[0] - def get_width(self): + def get_width(self) -> float: points = self.points return points[2, 0] - points[1, 0] - def get_height(self): + def get_height(self) -> float: points = self.points return points[4, 1] - points[3, 1] - def get_focal_distance(self): + def get_focal_distance(self) -> float: return self.focal_distance * self.get_height() def interpolate(self, *args, **kwargs): @@ -216,7 +224,9 @@ def interpolate(self, *args, **kwargs): class OpenGLRenderer: - def __init__(self, file_writer_class=SceneFileWriter, skip_animations=False): + def __init__( + self, file_writer_class=SceneFileWriter, skip_animations=False + ) -> None: # Measured in pixel widths, used for vector graphics self.anti_alias_width = 1.5 self._file_writer_class = file_writer_class @@ -436,11 +446,13 @@ def play(self, scene, *args, **kwargs): self.time += scene.duration self.num_plays += 1 - def clear_screen(self): + def clear_screen(self) -> None: self.frame_buffer_object.clear(*self.background_color) self.window.swap_buffers() - def render(self, scene, frame_offset, moving_mobjects): + def render( + self, scene: Scene, frame_offset, moving_mobjects: list[Mobject] + ) -> None: self.update_frame(scene) if self.skip_animations: diff --git a/manim/renderer/shader.py b/manim/renderer/shader.py index a098ed30ca..352fe23136 100644 --- a/manim/renderer/shader.py +++ b/manim/renderer/shader.py @@ -4,7 +4,9 @@ import inspect import re import textwrap +from collections.abc import Generator from pathlib import Path +from typing import TYPE_CHECKING import moderngl import numpy as np @@ -12,6 +14,9 @@ from .. import config from ..utils import opengl +if TYPE_CHECKING: + from typing_extensions import Self + SHADER_FOLDER = Path(__file__).parent / "shaders" shader_program_cache: dict = {} file_path_to_code_map: dict = {} @@ -143,7 +148,7 @@ def get_meshes(self): yield parent dfs.extend(parent.children) - def get_family(self): + def get_family(self) -> Generator[Object3D]: dfs = [self] while dfs: parent = dfs.pop() @@ -181,7 +186,7 @@ def init_updaters(self): self.has_updaters = False self.updating_suspended = False - def update(self, dt=0): + def update(self, dt: float = 0) -> Self: if not self.has_updaters or self.updating_suspended: return self for updater in self.time_based_updaters: diff --git a/manim/scene/moving_camera_scene.py b/manim/scene/moving_camera_scene.py index eafc992ef5..8859a4d154 100644 --- a/manim/scene/moving_camera_scene.py +++ b/manim/scene/moving_camera_scene.py @@ -89,6 +89,8 @@ def create_scene(number): __all__ = ["MovingCameraScene"] +from typing import TYPE_CHECKING + from manim.animation.animation import Animation from ..camera.moving_camera import MovingCamera @@ -96,6 +98,11 @@ def create_scene(number): from ..utils.family import extract_mobject_family_members from ..utils.iterables import list_update +if TYPE_CHECKING: + from typing_extensions import Any + + from manim.mobject.mobject import Mobject + class MovingCameraScene(Scene): """ @@ -111,10 +118,12 @@ class MovingCameraScene(Scene): :class:`.MovingCamera` """ - def __init__(self, camera_class=MovingCamera, **kwargs): + def __init__( + self, camera_class: type[MovingCamera] = MovingCamera, **kwargs: Any + ) -> None: super().__init__(camera_class=camera_class, **kwargs) - def get_moving_mobjects(self, *animations: Animation): + def get_moving_mobjects(self, *animations: Animation) -> list[Mobject]: """ This method returns a list of all of the Mobjects in the Scene that are moving, that are also in the animations passed. diff --git a/manim/scene/scene.py b/manim/scene/scene.py index 02c548cf7f..d7db448c70 100644 --- a/manim/scene/scene.py +++ b/manim/scene/scene.py @@ -16,7 +16,7 @@ import types from queue import Queue -import srt +import srt # type: ignore[import-untyped] from manim.scene.section import DefaultSectionType @@ -42,7 +42,7 @@ from ..constants import * from ..gui.gui import configure_pygui from ..renderer.cairo_renderer import CairoRenderer -from ..renderer.opengl_renderer import OpenGLRenderer +from ..renderer.opengl_renderer import OpenGLCamera, OpenGLRenderer from ..renderer.shader import Object3D from ..utils import opengl, space_ops from ..utils.exceptions import EndSceneEarlyException, RerunSceneException @@ -53,17 +53,22 @@ if TYPE_CHECKING: from collections.abc import Iterable, Sequence - from typing import Callable + from types import FrameType + from typing import Any, Callable + + from typing_extensions import Self + + from manim.typing import InternalPoint3D class RerunSceneHandler(FileSystemEventHandler): """A class to handle rerunning a Scene after the input file is modified.""" - def __init__(self, queue): + def __init__(self, queue: Queue) -> None: super().__init__() self.queue = queue - def on_modified(self, event): + def on_modified(self, event: Any) -> None: self.queue.put(("rerun_file", [], {})) @@ -100,35 +105,41 @@ def construct(self): def __init__( self, - renderer=None, - camera_class=Camera, - always_update_mobjects=False, - random_seed=None, - skip_animations=False, - ): + renderer: CairoRenderer | OpenGLRenderer | None = None, + camera_class: type[Camera] = Camera, + always_update_mobjects: bool = False, + random_seed: int | None = None, + skip_animations: bool = False, + ) -> None: self.camera_class = camera_class self.always_update_mobjects = always_update_mobjects self.random_seed = random_seed self.skip_animations = skip_animations - self.animations = None - self.stop_condition = None - self.moving_mobjects = [] - self.static_mobjects = [] - self.time_progression = None - self.duration = None - self.last_t = None - self.queue = Queue() + # TODO: We should probably change the default value to the empty list. + # This would remove several type issues, but it also triggers a lot of + # errors in the unittests (pytest) + self.animations: list[Animation] = [] + self.stop_condition: Callable[[], bool] | None = None + self.moving_mobjects: list[Mobject] = [] + self.static_mobjects: list[Mobject] = [] + self.time_progression: tqdm[float] = None + self.duration: float | None = None + # TODO: We should probably change the default value to 0, to avoid + # handling the case where the value is None. + # This change triggers no errors in the unittests (pytest). + self.last_t: float = 0 + self.queue: Queue = Queue() self.skip_animation_preview = False - self.meshes = [] + self.meshes: list[Object3D] = [] self.camera_target = ORIGIN - self.widgets = [] + self.widgets: list[Any] = [] self.dearpygui_imported = dearpygui_imported - self.updaters = [] - self.point_lights = [] + self.updaters: list[Callable[[float], None]] = [] + self.point_lights: list[Any] = [] self.ambient_light = None - self.key_to_function_map = {} - self.mouse_press_callbacks = [] + self.key_to_function_map: dict[str, Callable[[None], None]] = {} + self.mouse_press_callbacks: list[Callable[[], None]] = [] self.interactive_mode = False if config.renderer == RendererType.OPENGL: @@ -139,7 +150,9 @@ def __init__( renderer = OpenGLRenderer() if renderer is None: - self.renderer = CairoRenderer( + self.renderer: CairoRenderer | OpenGLRenderer = CairoRenderer( + # TODO: Is it a suitable approach to make an instance of + # the self.camera_class here? camera_class=self.camera_class, skip_animations=self.skip_animations, ) @@ -147,18 +160,18 @@ def __init__( self.renderer = renderer self.renderer.init_scene(self) - self.mobjects = [] + self.mobjects: list[Mobject] = [] # TODO, remove need for foreground mobjects - self.foreground_mobjects = [] + self.foreground_mobjects: list[Mobject] = [] if self.random_seed is not None: random.seed(self.random_seed) np.random.seed(self.random_seed) @property - def camera(self): + def camera(self) -> Camera | OpenGLCamera: return self.renderer.camera - def __deepcopy__(self, clone_from_id): + def __deepcopy__(self, clone_from_id: list[Any]) -> Scene: cls = self.__class__ result = cls.__new__(cls) clone_from_id[id(self)] = result @@ -168,7 +181,6 @@ def __deepcopy__(self, clone_from_id): if k == "camera_class": setattr(result, k, v) setattr(result, k, copy.deepcopy(v, clone_from_id)) - result.mobject_updater_lists = [] # Update updaters for mobject in self.mobjects: @@ -212,11 +224,9 @@ def __deepcopy__(self, clone_from_id): cloned_updaters.append(cloned_updater) mobject_clone = clone_from_id[id(mobject)] mobject_clone.updaters = cloned_updaters - if len(cloned_updaters) > 0: - result.mobject_updater_lists.append((mobject_clone, cloned_updaters)) return result - def render(self, preview: bool = False): + def render(self, preview: bool = False) -> bool: """ Renders this Scene. @@ -232,6 +242,7 @@ def render(self, preview: bool = False): pass except RerunSceneException: self.remove(*self.mobjects) + # TODO: The CairoRenderer does not have the method clear_screen() self.renderer.clear_screen() self.renderer.num_plays = 0 return True @@ -256,7 +267,9 @@ def render(self, preview: bool = False): if config["preview"] or config["show_in_file_browser"]: open_media_file(self.renderer.file_writer) - def setup(self): + # TODO: What value should the function return when it reaches this point? + + def setup(self) -> None: """ This is meant to be implemented by any scenes which are commonly subclassed, and have some common setup @@ -264,7 +277,7 @@ def setup(self): """ pass - def tear_down(self): + def tear_down(self) -> None: """ This is meant to be implemented by any scenes which are commonly subclassed, and have some common method @@ -272,7 +285,7 @@ def tear_down(self): """ pass - def construct(self): + def construct(self) -> None: """Add content to the Scene. From within :meth:`Scene.construct`, display mobjects on screen by calling @@ -317,10 +330,10 @@ def next_section( """ self.renderer.file_writer.next_section(name, section_type, skip_animations) - def __str__(self): + def __str__(self) -> str: return self.__class__.__name__ - def get_attrs(self, *keys: str): + def get_attrs(self, *keys: str) -> list[Any]: """ Gets attributes of a scene given the attribute's identifier/name. @@ -336,7 +349,7 @@ def get_attrs(self, *keys: str): """ return [getattr(self, key) for key in keys] - def update_mobjects(self, dt: float): + def update_mobjects(self, dt: float) -> None: """ Begins updating all mobjects in the Scene. @@ -348,12 +361,12 @@ def update_mobjects(self, dt: float): for mobject in self.mobjects: mobject.update(dt) - def update_meshes(self, dt): + def update_meshes(self, dt: float) -> None: for obj in self.meshes: for mesh in obj.get_family(): mesh.update(dt) - def update_self(self, dt: float): + def update_self(self, dt: float) -> None: """Run all scene updater functions. Among all types of update functions (mobject updaters, mesh updaters, @@ -385,7 +398,9 @@ def should_update_mobjects(self) -> bool: This is only called when a single Wait animation is played. """ + assert isinstance(self.animations, list) wait_animation = self.animations[0] + assert isinstance(wait_animation, Wait) if wait_animation.is_static_wait is None: should_update = ( self.always_update_mobjects @@ -399,7 +414,7 @@ def should_update_mobjects(self) -> bool: wait_animation.is_static_wait = not should_update return not wait_animation.is_static_wait - def get_top_level_mobjects(self): + def get_top_level_mobjects(self) -> list[Mobject]: """ Returns all mobjects which are not submobjects. @@ -412,13 +427,13 @@ def get_top_level_mobjects(self): # of another mobject from the scene families = [m.get_family() for m in self.mobjects] - def is_top_level(mobject): + def is_top_level(mobject: Mobject) -> bool: num_families = sum((mobject in family) for family in families) return num_families == 1 return list(filter(is_top_level, self.mobjects)) - def get_mobject_family_members(self): + def get_mobject_family_members(self) -> list[Mobject]: """ Returns list of family-members of all mobjects in scene. If a Circle() and a VGroup(Rectangle(),Triangle()) were added, @@ -441,7 +456,7 @@ def get_mobject_family_members(self): use_z_index=self.renderer.camera.use_z_index, ) - def add(self, *mobjects: Mobject): + def add(self, *mobjects: Mobject | OpenGLMobject) -> Self: """ Mobjects will be displayed, from background to foreground in the order with which they are added. @@ -459,7 +474,7 @@ def add(self, *mobjects: Mobject): """ if config.renderer == RendererType.OPENGL: new_mobjects = [] - new_meshes = [] + new_meshes: list[Object3D] = [] for mobject_or_mesh in mobjects: if isinstance(mobject_or_mesh, Object3D): new_meshes.append(mobject_or_mesh) @@ -470,18 +485,21 @@ def add(self, *mobjects: Mobject): self.remove(*new_meshes) self.meshes += new_meshes elif config.renderer == RendererType.CAIRO: - mobjects = [*mobjects, *self.foreground_mobjects] - self.restructure_mobjects(to_remove=mobjects) - self.mobjects += mobjects + new_and_foreground_mobjects: list[Mobject] = [ + *mobjects, + *self.foreground_mobjects, + ] + self.restructure_mobjects(to_remove=new_and_foreground_mobjects) + self.mobjects += new_and_foreground_mobjects if self.moving_mobjects: self.restructure_mobjects( - to_remove=mobjects, + to_remove=new_and_foreground_mobjects, mobject_list_name="moving_mobjects", ) - self.moving_mobjects += mobjects + self.moving_mobjects += new_and_foreground_mobjects return self - def add_mobjects_from_animations(self, animations): + def add_mobjects_from_animations(self, animations: list[Animation]) -> None: curr_mobjects = self.get_mobject_family_members() for animation in animations: if animation.is_introducer(): @@ -493,7 +511,7 @@ def add_mobjects_from_animations(self, animations): self.add(mob) curr_mobjects += mob.get_family() - def remove(self, *mobjects: Mobject): + def remove(self, *mobjects: Mobject) -> Self: """ Removes mobjects in the passed list of mobjects from the scene and the foreground, by removing them @@ -506,7 +524,7 @@ def remove(self, *mobjects: Mobject): """ if config.renderer == RendererType.OPENGL: mobjects_to_remove = [] - meshes_to_remove = set() + meshes_to_remove: set[Object3D] = set() for mobject_or_mesh in mobjects: if isinstance(mobject_or_mesh, Object3D): meshes_to_remove.add(mobject_or_mesh) @@ -516,8 +534,12 @@ def remove(self, *mobjects: Mobject): self.mobjects, mobjects_to_remove, ) + + def lambda_function(mesh: Object3D) -> bool: + return mesh not in set(meshes_to_remove) + self.meshes = list( - filter(lambda mesh: mesh not in set(meshes_to_remove), self.meshes), + filter(lambda_function, self.meshes), ) return self elif config.renderer == RendererType.CAIRO: @@ -623,7 +645,7 @@ def restructure_mobjects( to_remove: Sequence[Mobject], mobject_list_name: str = "mobjects", extract_families: bool = True, - ): + ) -> Scene: """ tl:wr If your scene has a Group(), and you removed a mobject from the Group, @@ -661,7 +683,9 @@ def restructure_mobjects( setattr(self, mobject_list_name, new_list) return self - def get_restructured_mobject_list(self, mobjects: list, to_remove: list): + def get_restructured_mobject_list( + self, mobjects: list[Mobject], to_remove: Sequence[Mobject] + ) -> list[Mobject]: """ Given a list of mobjects and a list of mobjects to be removed, this filters out the removable mobjects from the list of mobjects. @@ -680,9 +704,11 @@ def get_restructured_mobject_list(self, mobjects: list, to_remove: list): list The list of mobjects with the mobjects to remove removed. """ - new_mobjects = [] + new_mobjects: list[Mobject] = [] - def add_safe_mobjects_from_list(list_to_examine, set_to_remove): + def add_safe_mobjects_from_list( + list_to_examine: list[Mobject], set_to_remove: set[Mobject] + ) -> None: for mob in list_to_examine: if mob in set_to_remove: continue @@ -696,7 +722,7 @@ def add_safe_mobjects_from_list(list_to_examine, set_to_remove): return new_mobjects # TODO, remove this, and calls to this - def add_foreground_mobjects(self, *mobjects: Mobject): + def add_foreground_mobjects(self, *mobjects: Mobject) -> Scene: """ Adds mobjects to the foreground, and internally to the list foreground_mobjects, and mobjects. @@ -715,7 +741,7 @@ def add_foreground_mobjects(self, *mobjects: Mobject): self.add(*mobjects) return self - def add_foreground_mobject(self, mobject: Mobject): + def add_foreground_mobject(self, mobject: Mobject) -> Scene: """ Adds a single mobject to the foreground, and internally to the list foreground_mobjects, and mobjects. @@ -732,7 +758,7 @@ def add_foreground_mobject(self, mobject: Mobject): """ return self.add_foreground_mobjects(mobject) - def remove_foreground_mobjects(self, *to_remove: Mobject): + def remove_foreground_mobjects(self, *to_remove: Mobject) -> Scene: """ Removes mobjects from the foreground, and internally from the list foreground_mobjects. @@ -750,7 +776,7 @@ def remove_foreground_mobjects(self, *to_remove: Mobject): self.restructure_mobjects(to_remove, "foreground_mobjects") return self - def remove_foreground_mobject(self, mobject: Mobject): + def remove_foreground_mobject(self, mobject: Mobject) -> Scene: """ Removes a single mobject from the foreground, and internally from the list foreground_mobjects. @@ -767,7 +793,7 @@ def remove_foreground_mobject(self, mobject: Mobject): """ return self.remove_foreground_mobjects(mobject) - def bring_to_front(self, *mobjects: Mobject): + def bring_to_front(self, *mobjects: Mobject) -> Scene: """ Adds the passed mobjects to the scene again, pushing them to he front of the scene. @@ -786,7 +812,7 @@ def bring_to_front(self, *mobjects: Mobject): self.add(*mobjects) return self - def bring_to_back(self, *mobjects: Mobject): + def bring_to_back(self, *mobjects: Mobject) -> Scene: """ Removes the mobject from the scene and adds them to the back of the scene. @@ -806,7 +832,7 @@ def bring_to_back(self, *mobjects: Mobject): self.mobjects = list(mobjects) + self.mobjects return self - def clear(self): + def clear(self) -> Self: """ Removes all mobjects present in self.mobjects and self.foreground_mobjects from the scene. @@ -822,7 +848,7 @@ def clear(self): self.foreground_mobjects = [] return self - def get_moving_mobjects(self, *animations: Animation): + def get_moving_mobjects(self, *animations: Animation) -> list[Mobject]: """ Gets all moving mobjects in the passed animation(s). @@ -853,7 +879,9 @@ def get_moving_mobjects(self, *animations: Animation): return mobjects[i:] return [] - def get_moving_and_static_mobjects(self, animations): + def get_moving_and_static_mobjects( + self, animations: list[Animation] + ) -> tuple[list[Mobject], list[Mobject]]: all_mobjects = list_update(self.mobjects, self.foreground_mobjects) all_mobject_families = extract_mobject_family_members( all_mobjects, @@ -873,9 +901,12 @@ def get_moving_and_static_mobjects(self, animations): def compile_animations( self, - *args: Animation | Iterable[Animation] | types.GeneratorType[Animation], - **kwargs, - ): + # TODO: Consider to remove the part with the types.GeneratorType + *args: Animation + | Iterable[Animation] + | types.GeneratorType[Animation, None, None], + **kwargs: Any, + ) -> list[Animation]: """ Creates _MethodAnimations from any _AnimationBuilders and updates animation kwargs with kwargs passed to play(). @@ -917,7 +948,7 @@ def compile_animations( def _get_animation_time_progression( self, animations: list[Animation], duration: float - ): + ) -> tqdm[float]: """ You will hardly use this when making your own animations. This method is for Manim's internal use. @@ -970,10 +1001,10 @@ def _get_animation_time_progression( def get_time_progression( self, run_time: float, - description, + description: str, n_iterations: int | None = None, override_skip_animations: bool = False, - ): + ) -> tqdm[float]: """ You will hardly use this when making your own animations. This method is for Manim's internal use. @@ -1001,7 +1032,7 @@ def get_time_progression( The CommandLine Progress Bar. """ if self.renderer.skip_animations and not override_skip_animations: - times = [run_time] + times: Iterable[float] = [run_time] else: step = 1 / config["frame_rate"] times = np.arange(0, run_time, step) @@ -1015,7 +1046,7 @@ def get_time_progression( ) return time_progression - def get_run_time(self, animations: list[Animation]): + def get_run_time(self, animations: list[Animation]) -> float: """ Gets the total run time for a list of animations. @@ -1030,7 +1061,7 @@ def get_run_time(self, animations: list[Animation]): float The total ``run_time`` of all of the animations in the list. """ - max_run_time = 0 + max_run_time: float = 0 frame_rate = ( 1 / config.frame_rate ) # config.frame_rate holds the number of frames per second @@ -1055,12 +1086,14 @@ def get_run_time(self, animations: list[Animation]): def play( self, - *args: Animation | Iterable[Animation] | types.GeneratorType[Animation], - subcaption=None, - subcaption_duration=None, - subcaption_offset=0, - **kwargs, - ): + *args: Animation + | Iterable[Animation] + | types.GeneratorType[Animation, None, None], + subcaption: str | None = None, + subcaption_duration: float | None = None, + subcaption_offset: float = 0, + **kwargs: Any, + ) -> None: r"""Plays an animation in this scene. Parameters @@ -1125,7 +1158,7 @@ def wait( duration: float = DEFAULT_WAIT_TIME, stop_condition: Callable[[], bool] | None = None, frozen_frame: bool | None = None, - ): + ) -> None: """Plays a "no operation" animation. Parameters @@ -1155,7 +1188,7 @@ def wait( ) ) - def pause(self, duration: float = DEFAULT_WAIT_TIME): + def pause(self, duration: float = DEFAULT_WAIT_TIME) -> None: """Pauses the scene (i.e., displays a frozen frame). This is an alias for :meth:`.wait` with ``frozen_frame`` @@ -1172,7 +1205,9 @@ def pause(self, duration: float = DEFAULT_WAIT_TIME): """ self.wait(duration=duration, frozen_frame=True) - def wait_until(self, stop_condition: Callable[[], bool], max_time: float = 60): + def wait_until( + self, stop_condition: Callable[[], bool], max_time: float = 60 + ) -> None: """Wait until a condition is satisfied, up to a given maximum duration. Parameters @@ -1187,9 +1222,11 @@ def wait_until(self, stop_condition: Callable[[], bool], max_time: float = 60): def compile_animation_data( self, - *animations: Animation | Iterable[Animation] | types.GeneratorType[Animation], - **play_kwargs, - ): + *animations: Animation + | Iterable[Animation] + | types.GeneratorType[Animation, None, None], + **play_kwargs: Any, + ) -> Self | None: """Given a list of animations, compile the corresponding static and moving mobjects, and gather the animation durations. @@ -1255,7 +1292,7 @@ def is_current_animation_frozen_frame(self) -> bool: and self.animations[0].is_static_wait ) - def play_internal(self, skip_rendering: bool = False): + def play_internal(self, skip_rendering: bool = False) -> None: """ This method is used to prep the animations for rendering, apply the arguments and parameters required to them, @@ -1284,11 +1321,13 @@ def play_internal(self, skip_rendering: bool = False): animation.clean_up_from_scene(self) if not self.renderer.skip_animations: self.update_mobjects(0) + # TODO: The OpenGLRenderer does not have the property static.image. self.renderer.static_image = None # Closing the progress bar at the end of the play. self.time_progression.close() - def check_interactive_embed_is_valid(self): + def check_interactive_embed_is_valid(self) -> bool: + assert isinstance(self.renderer, OpenGLRenderer) if config["force_window"]: return True if self.skip_animation_preview: @@ -1313,23 +1352,26 @@ def check_interactive_embed_is_valid(self): return False return True - def interactive_embed(self): + def interactive_embed(self) -> None: """Like embed(), but allows for screen interaction.""" + assert isinstance(self.camera, OpenGLCamera) if not self.check_interactive_embed_is_valid(): return self.interactive_mode = True - def ipython(shell, namespace): + def ipython(shell: InteractiveShellEmbed, namespace: dict[str, Any]) -> None: import manim.opengl - def load_module_into_namespace(module, namespace): + def load_module_into_namespace( + module: Any, namespace: dict[str, Any] + ) -> None: for name in dir(module): namespace[name] = getattr(module, name) load_module_into_namespace(manim, namespace) load_module_into_namespace(manim.opengl, namespace) - def embedded_rerun(*args, **kwargs): + def embedded_rerun(*args: Any, **kwargs: Any) -> None: self.queue.put(("rerun_keyboard", args, kwargs)) shell.exiter() @@ -1338,7 +1380,7 @@ def embedded_rerun(*args, **kwargs): shell(local_ns=namespace) self.queue.put(("exit_keyboard", [], {})) - def get_embedded_method(method_name): + def get_embedded_method(method_name: str) -> Callable: return lambda *args, **kwargs: self.queue.put((method_name, args, kwargs)) local_namespace = inspect.currentframe().f_back.f_locals @@ -1355,11 +1397,11 @@ def get_embedded_method(method_name): cfg = Config() cfg.TerminalInteractiveShell.confirm_exit = False - if get_ipython() is None: + if get_ipython() is None: # type: ignore[no-untyped-call] shell = InteractiveShellEmbed.instance(config=cfg) else: - shell = InteractiveShellEmbed(config=cfg) - hist = get_ipython().history_manager + shell = InteractiveShellEmbed(config=cfg) # type: ignore[no-untyped-call] + hist = get_ipython().history_manager # type: ignore[no-untyped-call] hist.db = connect(hist.hist_file, check_same_thread=False) keyboard_thread = threading.Thread( @@ -1386,7 +1428,12 @@ def get_embedded_method(method_name): self.interact(shell, keyboard_thread) - def interact(self, shell, keyboard_thread): + from IPython.terminal.embed import InteractiveShellEmbed + + def interact( + self, shell: InteractiveShellEmbed, keyboard_thread: threading.Thread + ) -> None: + assert isinstance(self.renderer, OpenGLRenderer) event_handler = RerunSceneHandler(self.queue) file_observer = Observer() file_observer.schedule(event_handler, config["input_file"], recursive=True) @@ -1463,7 +1510,8 @@ def interact(self, shell, keyboard_thread): if self.renderer.window.is_closing: self.renderer.window.destroy() - def embed(self): + def embed(self) -> None: + assert isinstance(self.renderer, OpenGLRenderer) if not config["preview"]: logger.warning("Called embed() while no preview window is available.") return @@ -1477,7 +1525,7 @@ def embed(self): # Configure IPython shell. from IPython.terminal.embed import InteractiveShellEmbed - shell = InteractiveShellEmbed() + shell = InteractiveShellEmbed() # type: ignore[no-untyped-call] # Have the frame update after each command shell.events.register( @@ -1487,7 +1535,9 @@ def embed(self): # Use the locals of the caller as the local namespace # once embedded, and add a few custom shortcuts. - local_ns = inspect.currentframe().f_back.f_locals + current_frame = inspect.currentframe() + assert isinstance(current_frame, FrameType) + local_ns = current_frame.f_back.f_locals # local_ns["touch"] = self.interact for method in ( "play", @@ -1505,7 +1555,7 @@ def embed(self): # End scene when exiting an embed. raise Exception("Exiting scene.") - def update_to_time(self, t): + def update_to_time(self, t: float) -> None: dt = t - self.last_t self.last_t = t for animation in self.animations: @@ -1571,8 +1621,8 @@ def add_sound( sound_file: str, time_offset: float = 0, gain: float | None = None, - **kwargs, - ): + **kwargs: Any, + ) -> None: """ This method is used to add a sound to the animation. @@ -1613,7 +1663,8 @@ def construct(self): time = self.renderer.time + time_offset self.renderer.file_writer.add_sound(sound_file, time, gain, **kwargs) - def on_mouse_motion(self, point, d_point): + def on_mouse_motion(self, point: InternalPoint3D, d_point: InternalPoint3D) -> None: + assert isinstance(self.camera, OpenGLCamera) self.mouse_point.move_to(point) if SHIFT_VALUE in self.renderer.pressed_keys: shift = -d_point @@ -1623,13 +1674,15 @@ def on_mouse_motion(self, point, d_point): shift = np.dot(np.transpose(transform), shift) self.camera.shift(shift) - def on_mouse_scroll(self, point, offset): + def on_mouse_scroll(self, point: InternalPoint3D, offset: InternalPoint3D) -> None: + assert isinstance(self.camera, OpenGLCamera) if not config.use_projection_stroke_shaders: factor = 1 + np.arctan(-2.1 * offset[1]) self.camera.scale(factor, about_point=self.camera_target) self.mouse_scroll_orbit_controls(point, offset) - def on_key_press(self, symbol, modifiers): + def on_key_press(self, symbol: int, modifiers: Any) -> None: + assert isinstance(self.camera, OpenGLCamera) try: char = chr(symbol) except OverflowError: @@ -1645,10 +1698,17 @@ def on_key_press(self, symbol, modifiers): if char in self.key_to_function_map: self.key_to_function_map[char]() - def on_key_release(self, symbol, modifiers): + def on_key_release(self, symbol: int, modifiers: Any) -> None: pass - def on_mouse_drag(self, point, d_point, buttons, modifiers): + def on_mouse_drag( + self, + point: InternalPoint3D, + d_point: InternalPoint3D, + buttons: int, + modifiers: Any, + ) -> None: + assert isinstance(self.camera, OpenGLCamera) self.mouse_drag_point.move_to(point) if buttons == 1: self.camera.increment_theta(-d_point[0]) @@ -1662,7 +1722,10 @@ def on_mouse_drag(self, point, d_point, buttons, modifiers): self.mouse_drag_orbit_controls(point, d_point, buttons, modifiers) - def mouse_scroll_orbit_controls(self, point, offset): + def mouse_scroll_orbit_controls( + self, point: InternalPoint3D, offset: InternalPoint3D + ) -> None: + assert isinstance(self.camera, OpenGLCamera) camera_to_target = self.camera_target - self.camera.get_position() camera_to_target *= np.sign(offset[1]) shift_vector = 0.01 * camera_to_target @@ -1670,7 +1733,14 @@ def mouse_scroll_orbit_controls(self, point, offset): opengl.translation_matrix(*shift_vector) @ self.camera.model_matrix ) - def mouse_drag_orbit_controls(self, point, d_point, buttons, modifiers): + def mouse_drag_orbit_controls( + self, + point: InternalPoint3D, + d_point: InternalPoint3D, + buttons: int, + modifiers: Any, + ) -> None: + assert isinstance(self.camera, OpenGLCamera) # Left click drag. if buttons == 1: # Translate to target the origin and rotate around the z axis. @@ -1743,9 +1813,11 @@ def mouse_drag_orbit_controls(self, point, d_point, buttons, modifiers): ) self.camera_target += total_shift_vector - def set_key_function(self, char, func): + def set_key_function(self, char: str, func: Callable[[None], Any]) -> None: self.key_to_function_map[char] = func - def on_mouse_press(self, point, button, modifiers): + def on_mouse_press( + self, point: InternalPoint3D, button: str, modifiers: Any + ) -> None: for func in self.mouse_press_callbacks: func() diff --git a/manim/scene/scene_file_writer.py b/manim/scene/scene_file_writer.py index 4293de7105..f2592b9931 100644 --- a/manim/scene/scene_file_writer.py +++ b/manim/scene/scene_file_writer.py @@ -15,7 +15,7 @@ import av import numpy as np -import srt +import srt # type: ignore[import-untyped] from PIL import Image from pydub import AudioSegment @@ -38,6 +38,7 @@ from .section import DefaultSectionType, Section if TYPE_CHECKING: + from manim.renderer.cairo_renderer import CairoRenderer from manim.renderer.opengl_renderer import OpenGLRenderer @@ -104,7 +105,9 @@ class SceneFileWriter: force_output_as_scene_name = False - def __init__(self, renderer, scene_name, **kwargs): + def __init__( + self, renderer: CairoRenderer | OpenGLRenderer, scene_name: str, **kwargs: Any + ): self.renderer = renderer self.init_output_directories(scene_name) self.init_audio() @@ -118,7 +121,7 @@ def __init__(self, renderer, scene_name, **kwargs): name="autocreated", type_=DefaultSectionType.NORMAL, skip_animations=False ) - def init_output_directories(self, scene_name): + def init_output_directories(self, scene_name: str) -> None: """Initialise output directories. Notes @@ -225,7 +228,7 @@ def next_section(self, name: str, type_: str, skip_animations: bool) -> None: ), ) - def add_partial_movie_file(self, hash_animation: str): + def add_partial_movie_file(self, hash_animation: str) -> None: """Adds a new partial movie file path to `scene.partial_movie_files` and current section from a hash. This method will compute the path from the hash. In addition to that it adds the new animation to the current section. @@ -250,7 +253,7 @@ def add_partial_movie_file(self, hash_animation: str): self.partial_movie_files.append(new_partial_movie_file) self.sections[-1].partial_movie_files.append(new_partial_movie_file) - def get_resolution_directory(self): + def get_resolution_directory(self) -> str: """Get the name of the resolution directory directly containing the video file. @@ -280,11 +283,11 @@ def get_resolution_directory(self): return f"{pixel_height}p{frame_rate}" # Sound - def init_audio(self): + def init_audio(self) -> None: """Preps the writer for adding audio to the movie.""" self.includes_sound = False - def create_audio_segment(self): + def create_audio_segment(self) -> None: """Creates an empty, silent, Audio Segment.""" self.audio_segment = AudioSegment.silent() @@ -293,7 +296,7 @@ def add_audio_segment( new_segment: AudioSegment, time: float | None = None, gain_to_background: float | None = None, - ): + ) -> None: """ This method adds an audio segment from an AudioSegment type object and suitable parameters. @@ -338,8 +341,8 @@ def add_sound( sound_file: str, time: float | None = None, gain: float | None = None, - **kwargs, - ): + **kwargs: Any, + ) -> None: """ This method adds an audio segment from a sound file. @@ -378,7 +381,9 @@ def add_sound( self.add_audio_segment(new_segment, time, **kwargs) # Writers - def begin_animation(self, allow_write: bool = False, file_path=None): + def begin_animation( + self, allow_write: bool = False, file_path: str | None = None + ) -> None: """ Used internally by manim to stream the animation to FFMPEG for displaying or writing to a file. @@ -391,7 +396,7 @@ def begin_animation(self, allow_write: bool = False, file_path=None): if write_to_movie() and allow_write: self.open_partial_movie_stream(file_path=file_path) - def end_animation(self, allow_write: bool = False): + def end_animation(self, allow_write: bool = False) -> None: """ Internally used by Manim to stop streaming to FFMPEG gracefully. @@ -404,7 +409,7 @@ def end_animation(self, allow_write: bool = False): if write_to_movie() and allow_write: self.close_partial_movie_stream() - def listen_and_write(self): + def listen_and_write(self) -> None: """For internal use only: blocks until new frame is available on the queue.""" while True: num_frames, frame_data = self.queue.get() @@ -431,7 +436,7 @@ def encode_and_write_frame(self, frame: PixelArray, num_frames: int) -> None: def write_frame( self, frame_or_renderer: np.ndarray | OpenGLRenderer, num_frames: int = 1 - ): + ) -> None: """ Used internally by Manim to write a frame to the FFMPEG input buffer. @@ -454,7 +459,7 @@ def write_frame( self.queue.put(msg) if is_png_format() and not config["dry_run"]: - image: Image = ( + image: Image.Image = ( frame_or_renderer.get_image() if config.renderer == RendererType.OPENGL else Image.fromarray(frame_or_renderer) @@ -468,14 +473,16 @@ def write_frame( config["zero_pad"], ) - def output_image(self, image: Image.Image, target_dir, ext, zero_pad: bool): + def output_image( + self, image: Image.Image, target_dir: Path, ext: str, zero_pad: bool + ) -> None: if zero_pad: image.save(f"{target_dir}{str(self.frame_count).zfill(zero_pad)}{ext}") else: image.save(f"{target_dir}{self.frame_count}{ext}") self.frame_count += 1 - def save_final_image(self, image: np.ndarray): + def save_final_image(self, image: Image.Image) -> None: """ The name is a misnomer. This method saves the image passed to it as an in the default image directory. @@ -516,7 +523,7 @@ def finish(self) -> None: if self.subcaptions: self.write_subcaption_file() - def open_partial_movie_stream(self, file_path=None) -> None: + def open_partial_movie_stream(self, file_path: str | None = None) -> None: """Open a container holding a video stream. This is used internally by Manim initialize the container holding @@ -546,7 +553,7 @@ def open_partial_movie_stream(self, file_path=None) -> None: partial_movie_file_pix_fmt = "argb" with av.open(file_path, mode="w") as video_container: - stream = video_container.add_stream( + stream: Any = video_container.add_stream( partial_movie_file_codec, rate=fps, options=av_options, @@ -582,7 +589,7 @@ def close_partial_movie_stream(self) -> None: {"path": f"'{self.partial_movie_file_path}'"}, ) - def is_already_cached(self, hash_invocation: str): + def is_already_cached(self, hash_invocation: str) -> bool: """Will check if a file named with `hash_invocation` exists. Parameters @@ -607,9 +614,9 @@ def combine_files( self, input_files: list[str], output_file: Path, - create_gif=False, - includes_sound=False, - ): + create_gif: bool = False, + includes_sound: bool = False, + ) -> None: file_list = self.partial_movie_directory / "partial_movie_file_list.txt" logger.debug( f"Partial movie files to combine ({len(input_files)} files): %(p)s", @@ -636,7 +643,7 @@ def combine_files( output_container.metadata["comment"] = ( f"Rendered with Manim Community v{__version__}" ) - output_stream = output_container.add_stream( + output_stream: Any = output_container.add_stream( codec_name="gif" if create_gif else None, template=partial_movies_stream if not create_gif else None, ) @@ -708,7 +715,7 @@ def combine_files( partial_movies_input.close() output_container.close() - def combine_to_movie(self): + def combine_to_movie(self) -> None: """Used internally by Manim to combine the separate partial movie files that make up a Scene into a single video file for that Scene. @@ -806,7 +813,7 @@ def combine_to_movie(self): shutil.move(str(temp_file_path), str(movie_file_path)) sound_file_path.unlink() - self.print_file_ready_message(str(movie_file_path)) + self.print_file_ready_message(movie_file_path) if write_to_movie(): for file_path in partial_movie_files: # We have to modify the accessed time so if we have to clean the cache we remove the one used the longest. @@ -828,7 +835,7 @@ def combine_to_section_videos(self) -> None: with (self.sections_output_dir / f"{self.output_name}.json").open("w") as file: json.dump(sections_index, file, indent=4) - def clean_cache(self): + def clean_cache(self) -> None: """Will clean the cache by removing the oldest partial_movie_files.""" cached_partial_movies = [ (self.partial_movie_directory / file_name) @@ -850,7 +857,7 @@ def clean_cache(self): " You can change this behaviour by changing max_files_cached in config.", ) - def flush_cache_directory(self): + def flush_cache_directory(self) -> None: """Delete all the cached partial movie files""" cached_partial_movies = [ self.partial_movie_directory / file_name @@ -864,7 +871,7 @@ def flush_cache_directory(self): {"par_dir": self.partial_movie_directory}, ) - def write_subcaption_file(self): + def write_subcaption_file(self) -> None: """Writes the subcaption file.""" if config.output_file is None: return @@ -872,7 +879,7 @@ def write_subcaption_file(self): subcaption_file.write_text(srt.compose(self.subcaptions), encoding="utf-8") logger.info(f"Subcaption file has been written as {subcaption_file}") - def print_file_ready_message(self, file_path): + def print_file_ready_message(self, file_path: Path) -> None: """Prints the "File Ready" message to STDOUT.""" config["output_file"] = file_path logger.info("\nFile ready at %(file_path)s\n", {"file_path": f"'{file_path}'"}) diff --git a/manim/scene/section.py b/manim/scene/section.py index af005b52da..728104f32e 100644 --- a/manim/scene/section.py +++ b/manim/scene/section.py @@ -100,5 +100,5 @@ def get_dict(self, sections_dir: Path) -> dict[str, Any]: **video_metadata, ) - def __repr__(self): + def __repr__(self) -> str: return f"
" diff --git a/manim/scene/three_d_scene.py b/manim/scene/three_d_scene.py index 7f39f4cf32..f79f637e7a 100644 --- a/manim/scene/three_d_scene.py +++ b/manim/scene/three_d_scene.py @@ -7,6 +7,8 @@ import warnings from collections.abc import Iterable, Sequence +from itertools import chain +from typing import TYPE_CHECKING import numpy as np @@ -23,10 +25,14 @@ from ..constants import DEGREES, RendererType from ..mobject.mobject import Mobject from ..mobject.types.vectorized_mobject import VectorizedPoint, VGroup -from ..renderer.opengl_renderer import OpenGLCamera from ..scene.scene import Scene from ..utils.config_ops import merge_dicts_recursively +if TYPE_CHECKING: + from typing_extensions import Any + + from ..renderer.opengl_renderer import OpenGLCamera + class ThreeDScene(Scene): """ @@ -36,10 +42,10 @@ class ThreeDScene(Scene): def __init__( self, - camera_class=ThreeDCamera, - ambient_camera_rotation=None, - default_angled_camera_orientation_kwargs=None, - **kwargs, + camera_class: type[ThreeDCamera] = ThreeDCamera, + ambient_camera_rotation: Any = None, + default_angled_camera_orientation_kwargs: dict[str, Any] | None = None, + **kwargs: Any, ): self.ambient_camera_rotation = ambient_camera_rotation if default_angled_camera_orientation_kwargs is None: @@ -60,8 +66,8 @@ def set_camera_orientation( zoom: float | None = None, focal_distance: float | None = None, frame_center: Mobject | Sequence[float] | None = None, - **kwargs, - ): + **kwargs: Any, + ) -> None: """ This method sets the orientation of the camera in the scene. @@ -86,6 +92,7 @@ def set_camera_orientation( The new center of the camera frame in cartesian coordinates. """ + assert isinstance(self.renderer.camera, ThreeDCamera) if phi is not None: self.renderer.camera.set_phi(phi) if theta is not None: @@ -99,7 +106,9 @@ def set_camera_orientation( if frame_center is not None: self.renderer.camera._frame_center.move_to(frame_center) - def begin_ambient_camera_rotation(self, rate: float = 0.02, about: str = "theta"): + def begin_ambient_camera_rotation( + self, rate: float = 0.02, about: str = "theta" + ) -> None: """ This method begins an ambient rotation of the camera about the Z_AXIS, in the anticlockwise direction @@ -114,7 +123,7 @@ def begin_ambient_camera_rotation(self, rate: float = 0.02, about: str = "theta" """ # TODO, use a ValueTracker for rate, so that it # can begin and end smoothly - about: str = about.lower() + about = about.lower() try: if config.renderer == RendererType.CAIRO: trackers = { @@ -132,14 +141,18 @@ def begin_ambient_camera_rotation(self, rate: float = 0.02, about: str = "theta" "phi": cam.increment_phi, "gamma": cam.increment_gamma, } - cam.add_updater(lambda m, dt: methods[about](rate * dt)) + + def updater(m: Mobject, dt: float) -> None: + methods[about](rate * dt) + + cam.add_updater(updater) self.add(self.camera) except Exception as e: raise ValueError("Invalid ambient rotation angle.") from e - def stop_ambient_camera_rotation(self, about="theta"): + def stop_ambient_camera_rotation(self, about: str = "theta") -> None: """This method stops all ambient camera rotation.""" - about: str = about.lower() + about = about.lower() try: if config.renderer == RendererType.CAIRO: trackers = { @@ -160,7 +173,7 @@ def begin_3dillusion_camera_rotation( rate: float = 1, origin_phi: float | None = None, origin_theta: float | None = None, - ): + ) -> None: """ This method creates a 3D camera rotation illusion around the current camera orientation. @@ -176,6 +189,7 @@ def begin_3dillusion_camera_rotation( The azimutal angle the camera should move around. Defaults to the current theta angle. """ + assert isinstance(self.renderer.camera, ThreeDCamera) if origin_theta is None: origin_theta = self.renderer.camera.theta_tracker.get_value() if origin_phi is None: @@ -183,7 +197,7 @@ def begin_3dillusion_camera_rotation( val_tracker_theta = ValueTracker(0) - def update_theta(m, dt): + def update_theta(m: ValueTracker, dt: float) -> ValueTracker: val_tracker_theta.increment_value(dt * rate) val_for_left_right = 0.2 * np.sin(val_tracker_theta.get_value()) return m.set_value(origin_theta + val_for_left_right) @@ -193,7 +207,7 @@ def update_theta(m, dt): val_tracker_phi = ValueTracker(0) - def update_phi(m, dt): + def update_phi(m: ValueTracker, dt: float) -> ValueTracker: val_tracker_phi.increment_value(dt * rate) val_for_up_down = 0.1 * np.cos(val_tracker_phi.get_value()) - 0.1 return m.set_value(origin_phi + val_for_up_down) @@ -201,8 +215,9 @@ def update_phi(m, dt): self.renderer.camera.phi_tracker.add_updater(update_phi) self.add(self.renderer.camera.phi_tracker) - def stop_3dillusion_camera_rotation(self): + def stop_3dillusion_camera_rotation(self) -> None: """This method stops all illusion camera rotations.""" + assert isinstance(self.renderer.camera, ThreeDCamera) self.renderer.camera.theta_tracker.clear_updaters() self.remove(self.renderer.camera.theta_tracker) self.renderer.camera.phi_tracker.clear_updaters() @@ -217,8 +232,8 @@ def move_camera( focal_distance: float | None = None, frame_center: Mobject | Sequence[float] | None = None, added_anims: Iterable[Animation] = [], - **kwargs, - ): + **kwargs: Any, + ) -> None: """ This method animates the movement of the camera to the given spherical coordinates. @@ -250,7 +265,6 @@ def move_camera( anims = [] if config.renderer == RendererType.CAIRO: - self.camera: ThreeDCamera value_tracker_pairs = [ (phi, self.camera.phi_tracker), (theta, self.camera.theta_tracker), @@ -264,7 +278,7 @@ def move_camera( if frame_center is not None: anims.append(self.camera._frame_center.animate.move_to(frame_center)) elif config.renderer == RendererType.OPENGL: - cam: OpenGLCamera = self.camera + cam: ThreeDCamera = self.camera cam2 = cam.copy() methods = { "theta": cam2.set_theta, @@ -297,10 +311,10 @@ def move_camera( "focal distance of OpenGLCamera can not be adjusted.", stacklevel=2, ) + # TODO: Clarify if the mapping below is correct (mobject = cam and taget_mobject = cam2) + anims += [Transform(mobject=cam, target_mobject=cam2)] - anims += [Transform(cam, cam2)] - - self.play(*anims + added_anims, **kwargs) + self.play(chain(*anims, added_anims), **kwargs) # These lines are added to improve performance. If manim thinks that frame_center is moving, # it is required to redraw every object. These lines remove frame_center from the Scene once @@ -309,7 +323,7 @@ def move_camera( if frame_center is not None and config.renderer == RendererType.CAIRO: self.remove(self.camera._frame_center) - def get_moving_mobjects(self, *animations: Animation): + def get_moving_mobjects(self, *animations: Animation) -> list[Mobject]: """ This method returns a list of all of the Mobjects in the Scene that are moving, that are also in the animations passed. @@ -319,6 +333,7 @@ def get_moving_mobjects(self, *animations: Animation): *animations The animations whose mobjects will be checked. """ + assert isinstance(self.renderer.camera, ThreeDCamera) moving_mobjects = super().get_moving_mobjects(*animations) camera_mobjects = self.renderer.camera.get_value_trackers() + [ self.renderer.camera._frame_center, @@ -327,7 +342,7 @@ def get_moving_mobjects(self, *animations: Animation): return self.mobjects return moving_mobjects - def add_fixed_orientation_mobjects(self, *mobjects: Mobject, **kwargs): + def add_fixed_orientation_mobjects(self, *mobjects: Mobject, **kwargs: Any) -> None: """ This method is used to prevent the rotation and tilting of mobjects as the camera moves around. The mobject can @@ -345,16 +360,17 @@ def add_fixed_orientation_mobjects(self, *mobjects: Mobject, **kwargs): use_static_center_func : bool center_func : function """ + assert isinstance(self.renderer.camera, ThreeDCamera) if config.renderer == RendererType.CAIRO: self.add(*mobjects) self.renderer.camera.add_fixed_orientation_mobjects(*mobjects, **kwargs) elif config.renderer == RendererType.OPENGL: + mob: Mobject for mob in mobjects: - mob: OpenGLMobject mob.fix_orientation() self.add(mob) - def add_fixed_in_frame_mobjects(self, *mobjects: Mobject): + def add_fixed_in_frame_mobjects(self, *mobjects: Mobject) -> None: """ This method is used to prevent the rotation and movement of mobjects as the camera moves around. The mobject is @@ -371,12 +387,12 @@ def add_fixed_in_frame_mobjects(self, *mobjects: Mobject): self.camera: ThreeDCamera self.camera.add_fixed_in_frame_mobjects(*mobjects) elif config.renderer == RendererType.OPENGL: + mob: Mobject for mob in mobjects: - mob: OpenGLMobject mob.fix_in_frame() self.add(mob) - def remove_fixed_orientation_mobjects(self, *mobjects: Mobject): + def remove_fixed_orientation_mobjects(self, *mobjects: Mobject) -> None: """ This method "unfixes" the orientation of the mobjects passed, meaning they will no longer be at the same angle @@ -388,15 +404,16 @@ def remove_fixed_orientation_mobjects(self, *mobjects: Mobject): *mobjects The Mobjects whose orientation must be unfixed. """ + assert isinstance(self.renderer.camera, ThreeDCamera) if config.renderer == RendererType.CAIRO: self.renderer.camera.remove_fixed_orientation_mobjects(*mobjects) elif config.renderer == RendererType.OPENGL: + mob: Mobject for mob in mobjects: - mob: OpenGLMobject mob.unfix_orientation() self.remove(mob) - def remove_fixed_in_frame_mobjects(self, *mobjects: Mobject): + def remove_fixed_in_frame_mobjects(self, *mobjects: Mobject) -> None: """ This method undoes what add_fixed_in_frame_mobjects does. It allows the mobject to be affected by the movement of @@ -407,16 +424,17 @@ def remove_fixed_in_frame_mobjects(self, *mobjects: Mobject): *mobjects The Mobjects whose position and orientation must be unfixed. """ + assert isinstance(self.renderer.camera, ThreeDCamera) if config.renderer == RendererType.CAIRO: self.renderer.camera.remove_fixed_in_frame_mobjects(*mobjects) elif config.renderer == RendererType.OPENGL: + mob: Mobject for mob in mobjects: - mob: OpenGLMobject mob.unfix_from_frame() self.remove(mob) ## - def set_to_default_angled_camera_orientation(self, **kwargs): + def set_to_default_angled_camera_orientation(self, **kwargs: Any) -> None: """ This method sets the default_angled_camera_orientation to the keyword arguments passed, and sets the camera to that orientation. @@ -449,9 +467,12 @@ class SpecialThreeDScene(ThreeDScene): def __init__( self, - cut_axes_at_radius=True, - camera_config={"should_apply_shading": True, "exponential_projection": True}, - three_d_axes_config={ + cut_axes_at_radius: bool = True, + camera_config: dict[str, Any] = { + "should_apply_shading": True, + "exponential_projection": True, + }, + three_d_axes_config: dict[str, Any] = { "num_axis_pieces": 1, "axis_config": { "unit_size": 2, @@ -460,20 +481,21 @@ def __init__( "stroke_width": 2, }, }, - sphere_config={"radius": 2, "resolution": (24, 48)}, - default_angled_camera_position={ + sphere_config: dict[str, Any] = {"radius": 2, "resolution": (24, 48)}, + default_angled_camera_position: dict[str, Any] = { "phi": 70 * DEGREES, "theta": -110 * DEGREES, }, # When scene is extracted with -l flag, this # configuration will override the above configuration. - low_quality_config={ + low_quality_config: dict[str, Any] = { "camera_config": {"should_apply_shading": False}, "three_d_axes_config": {"num_axis_pieces": 1}, "sphere_config": {"resolution": (12, 24)}, }, - **kwargs, - ): + **kwargs: Any, + ) -> None: + assert isinstance(self.renderer.camera, ThreeDCamera) self.cut_axes_at_radius = cut_axes_at_radius self.camera_config = camera_config self.three_d_axes_config = three_d_axes_config @@ -487,7 +509,7 @@ def __init__( _config = merge_dicts_recursively(_config, kwargs) super().__init__(**_config) - def get_axes(self): + def get_axes(self) -> ThreeDAxes: """Return a set of 3D axes. Returns @@ -511,7 +533,7 @@ def get_axes(self): tick.add(VectorizedPoint(1.5 * tick.get_center())) return axes - def get_sphere(self, **kwargs): + def get_sphere(self, **kwargs: Any) -> Sphere: """ Returns a sphere with the passed keyword arguments as properties. @@ -528,7 +550,7 @@ def get_sphere(self, **kwargs): config = merge_dicts_recursively(self.sphere_config, kwargs) return Sphere(**config) - def get_default_camera_position(self): + def get_default_camera_position(self) -> dict[str, Any]: """ Returns the default_angled_camera position. @@ -539,6 +561,6 @@ def get_default_camera_position(self): """ return self.default_angled_camera_position - def set_camera_to_default_position(self): + def set_camera_to_default_position(self) -> None: """Sets the camera to its default position.""" self.set_camera_orientation(**self.default_angled_camera_position) diff --git a/manim/scene/vector_space_scene.py b/manim/scene/vector_space_scene.py index be75151471..b31f2b11af 100644 --- a/manim/scene/vector_space_scene.py +++ b/manim/scene/vector_space_scene.py @@ -4,7 +4,7 @@ __all__ = ["VectorScene", "LinearTransformationScene"] -from typing import Callable +from typing import TYPE_CHECKING, Callable import numpy as np @@ -18,13 +18,14 @@ from .. import config from ..animation.animation import Animation -from ..animation.creation import Create, Write +from ..animation.creation import Create, DrawBorderThenFill, Write from ..animation.fading import FadeOut from ..animation.growing import GrowArrow from ..animation.transform import ApplyFunction, ApplyPointwiseFunction, Transform +from ..camera.camera import Camera from ..constants import * from ..mobject.matrix import Matrix -from ..mobject.mobject import Mobject +from ..mobject.mobject import Group, Mobject from ..mobject.types.vectorized_mobject import VGroup, VMobject from ..scene.scene import Scene from ..utils.color import ( @@ -41,6 +42,14 @@ from ..utils.rate_functions import rush_from, rush_into from ..utils.space_ops import angle_of_vector +if TYPE_CHECKING: + from typing import Any + + from typing_extensions import Self + + from manim.typing import InternalPoint3D + + X_COLOR = GREEN_C Y_COLOR = RED_C Z_COLOR = BLUE_D @@ -53,11 +62,11 @@ # Also, methods I would have thought of as getters, like coords_to_vector, are # actually doing a lot of animating. class VectorScene(Scene): - def __init__(self, basis_vector_stroke_width=6, **kwargs): + def __init__(self, basis_vector_stroke_width: float = 6, **kwargs: Any) -> None: super().__init__(**kwargs) self.basis_vector_stroke_width = basis_vector_stroke_width - def add_plane(self, animate: bool = False, **kwargs): + def add_plane(self, animate: bool = False, **kwargs: Any) -> NumberPlane: """ Adds a NumberPlane object to the background. @@ -79,7 +88,9 @@ def add_plane(self, animate: bool = False, **kwargs): self.add(plane) return plane - def add_axes(self, animate: bool = False, color: bool = WHITE, **kwargs): + def add_axes( + self, animate: bool = False, color: ParsableManimColor = WHITE, **kwargs: Any + ) -> Axes: """ Adds a pair of Axes to the Scene. @@ -96,7 +107,9 @@ def add_axes(self, animate: bool = False, color: bool = WHITE, **kwargs): self.add(axes) return axes - def lock_in_faded_grid(self, dimness: float = 0.7, axes_dimness: float = 0.5): + def lock_in_faded_grid( + self, dimness: float = 0.7, axes_dimness: float = 0.5 + ) -> None: """ This method freezes the NumberPlane and Axes that were already in the background, and adds new, manipulatable ones to the foreground. @@ -117,10 +130,14 @@ def lock_in_faded_grid(self, dimness: float = 0.7, axes_dimness: float = 0.5): self.add(axes) self.renderer.update_frame() - self.renderer.camera = Camera(self.renderer.get_frame()) + # TODO: To fix a type error, the output from get_frame() is + # sent to the background parameter of the Camera constructor. + self.renderer.camera = Camera(background=self.renderer.get_frame()) self.clear() - def get_vector(self, numerical_vector: np.ndarray | list | tuple, **kwargs): + def get_vector( + self, numerical_vector: np.ndarray | list | tuple, **kwargs: Any + ) -> Arrow: """ Returns an arrow on the Plane given an input numerical vector. @@ -146,10 +163,10 @@ def get_vector(self, numerical_vector: np.ndarray | list | tuple, **kwargs): def add_vector( self, vector: Arrow | list | tuple | np.ndarray, - color: str = YELLOW, + color: ParsableManimColor = YELLOW, animate: bool = True, - **kwargs, - ): + **kwargs: Any, + ) -> Arrow: """ Returns the Vector after adding it to the Plane. @@ -179,13 +196,13 @@ def add_vector( The arrow representing the vector. """ if not isinstance(vector, Arrow): - vector = Vector(vector, color=color, **kwargs) + vector = Vector(np.asarray(vector), color=color, **kwargs) if animate: self.play(GrowArrow(vector)) self.add(vector) return vector - def write_vector_coordinates(self, vector: Arrow, **kwargs): + def write_vector_coordinates(self, vector: Arrow, **kwargs: Any) -> Matrix: """ Returns a column matrix indicating the vector coordinates, after writing them to the screen. @@ -203,11 +220,15 @@ def write_vector_coordinates(self, vector: Arrow, **kwargs): :class:`.Matrix` The column matrix representing the vector. """ - coords = vector.coordinate_label(**kwargs) + coords: Matrix = vector.coordinate_label(**kwargs) self.play(Write(coords)) return coords - def get_basis_vectors(self, i_hat_color: str = X_COLOR, j_hat_color: str = Y_COLOR): + def get_basis_vectors( + self, + i_hat_color: ParsableManimColor = X_COLOR, + j_hat_color: ParsableManimColor = Y_COLOR, + ) -> VGroup: """ Returns a VGroup of the Basis Vectors (1,0) and (0,1) @@ -226,12 +247,16 @@ def get_basis_vectors(self, i_hat_color: str = X_COLOR, j_hat_color: str = Y_COL """ return VGroup( *( - Vector(vect, color=color, stroke_width=self.basis_vector_stroke_width) + Vector( + np.asarray(vect), + color=color, + stroke_width=self.basis_vector_stroke_width, + ) for vect, color in [([1, 0], i_hat_color), ([0, 1], j_hat_color)] ) ) - def get_basis_vector_labels(self, **kwargs): + def get_basis_vector_labels(self, **kwargs: Any) -> VGroup: """ Returns naming labels for the basis vectors. @@ -263,13 +288,13 @@ def get_basis_vector_labels(self, **kwargs): def get_vector_label( self, vector: Vector, - label, + label: MathTex | str, at_tip: bool = False, direction: str = "left", rotate: bool = False, - color: str | None = None, + color: ParsableManimColor | None = None, label_scale_factor: float = LARGE_BUFF - 0.2, - ): + ) -> MathTex: """ Returns naming labels for the passed vector. @@ -314,16 +339,18 @@ def get_vector_label( if not rotate: label.rotate(-angle, about_point=ORIGIN) if direction == "left": - label.shift(-label.get_bottom() + 0.1 * UP) + temp_shift_1: InternalPoint3D = np.asarray(label.get_bottom()) + label.shift(-temp_shift_1 + 0.1 * UP) else: - label.shift(-label.get_top() + 0.1 * DOWN) + temp_shift_2: InternalPoint3D = np.asarray(label.get_top()) + label.shift(-temp_shift_2 + 0.1 * DOWN) label.rotate(angle, about_point=ORIGIN) label.shift((vector.get_end() - vector.get_start()) / 2) return label def label_vector( - self, vector: Vector, label: MathTex | str, animate: bool = True, **kwargs - ): + self, vector: Vector, label: MathTex | str, animate: bool = True, **kwargs: Any + ) -> MathTex: """ Shortcut method for creating, and animating the addition of a label for the vector. @@ -355,30 +382,30 @@ def label_vector( def position_x_coordinate( self, - x_coord, - x_line, - vector, - ): # TODO Write DocStrings for this. + x_coord: Any, + x_line: Line, + vector: InternalPoint3D, + ) -> Any: # TODO Write DocStrings for this. x_coord.next_to(x_line, -np.sign(vector[1]) * UP) x_coord.set_color(X_COLOR) return x_coord def position_y_coordinate( self, - y_coord, - y_line, - vector, - ): # TODO Write DocStrings for this. + y_coord: Any, + y_line: Line, + vector: InternalPoint3D, + ) -> Any: # TODO Write DocStrings for this. y_coord.next_to(y_line, np.sign(vector[0]) * RIGHT) y_coord.set_color(Y_COLOR) return y_coord def coords_to_vector( self, - vector: np.ndarray | list | tuple, - coords_start: np.ndarray | list | tuple = 2 * RIGHT + 2 * UP, + vector: InternalPoint3D, + coords_start: InternalPoint3D = 2 * RIGHT + 2 * UP, clean_up: bool = True, - ): + ) -> None: """ This method writes the vector as a column matrix (henceforth called the label), takes the values in it one by one, and form the corresponding @@ -438,10 +465,10 @@ def coords_to_vector( def vector_to_coords( self, - vector: np.ndarray | list | tuple, + vector: InternalPoint3D, integer_labels: bool = True, clean_up: bool = True, - ): + ) -> tuple[Matrix, Line, Line]: """ This method displays vector as a Vector() based vector, and then shows the corresponding lines that make up the x and y components of the vector. @@ -499,7 +526,7 @@ def vector_to_coords( self.add(*starting_mobjects) return array, x_line, y_line - def show_ghost_movement(self, vector: Arrow | list | tuple | np.ndarray): + def show_ghost_movement(self, vector: InternalPoint3D | Arrow) -> None: """ This method plays an animation that partially shows the entire plane moving in the direction of a particular vector. This is useful when you wish to @@ -593,8 +620,8 @@ def __init__( i_hat_color: ParsableManimColor = X_COLOR, j_hat_color: ParsableManimColor = Y_COLOR, leave_ghost_vectors: bool = False, - **kwargs, - ): + **kwargs: Any, + ) -> None: super().__init__(**kwargs) self.include_background_plane = include_background_plane @@ -630,22 +657,22 @@ def __init__( ) @staticmethod - def update_default_configs(default_configs, passed_configs): + def update_default_configs(default_configs: Any, passed_configs: Any) -> None: for default_config, passed_config in zip(default_configs, passed_configs): if passed_config is not None: update_dict_recursively(default_config, passed_config) - def setup(self): + def setup(self) -> None: # The has_already_setup attr is to not break all the old Scenes if hasattr(self, "has_already_setup"): return - self.has_already_setup = True - self.background_mobjects = [] - self.foreground_mobjects = [] - self.transformable_mobjects = [] + self.has_already_setup: bool = True + self.background_mobjects: list[Mobject] = [] + self.foreground_mobjects: list[Mobject] = [] + self.transformable_mobjects: list[Mobject] = [] self.moving_vectors = [] - self.transformable_labels = [] - self.moving_mobjects = [] + self.transformable_labels: list[MathTex] = [] + self.moving_mobjects: list[Mobject] = [] self.background_plane = NumberPlane(**self.background_plane_kwargs) @@ -665,7 +692,7 @@ def setup(self): self.i_hat, self.j_hat = self.basis_vectors self.add(self.basis_vectors) - def add_special_mobjects(self, mob_list: list, *mobs_to_add: Mobject): + def add_special_mobjects(self, mob_list: list, *mobs_to_add: Mobject) -> None: """ Adds mobjects to a separate list that can be tracked, if these mobjects have some extra importance. @@ -685,7 +712,7 @@ def add_special_mobjects(self, mob_list: list, *mobs_to_add: Mobject): mob_list.append(mobject) self.add(mobject) - def add_background_mobject(self, *mobjects: Mobject): + def add_background_mobject(self, *mobjects: Mobject) -> None: """ Adds the mobjects to the special list self.background_mobjects. @@ -697,8 +724,9 @@ def add_background_mobject(self, *mobjects: Mobject): """ self.add_special_mobjects(self.background_mobjects, *mobjects) - # TODO, this conflicts with Scene.add_fore - def add_foreground_mobject(self, *mobjects: Mobject): + # TODO, this conflicts with Scene.add_foreground_mobject + # Please be aware that there is also the method Scene.add_foreground_mobjects. + def add_foreground_mobject(self, *mobjects: Mobject) -> None: """ Adds the mobjects to the special list self.foreground_mobjects. @@ -710,7 +738,7 @@ def add_foreground_mobject(self, *mobjects: Mobject): """ self.add_special_mobjects(self.foreground_mobjects, *mobjects) - def add_transformable_mobject(self, *mobjects: Mobject): + def add_transformable_mobject(self, *mobjects: Mobject) -> None: """ Adds the mobjects to the special list self.transformable_mobjects. @@ -724,7 +752,7 @@ def add_transformable_mobject(self, *mobjects: Mobject): def add_moving_mobject( self, mobject: Mobject, target_mobject: Mobject | None = None - ): + ) -> None: """ Adds the mobject to the special list self.moving_mobject, and adds a property @@ -751,8 +779,11 @@ def get_ghost_vectors(self) -> VGroup: return self.ghost_vectors def get_unit_square( - self, color: str = YELLOW, opacity: float = 0.3, stroke_width: float = 3 - ): + self, + color: ParsableManimColor = YELLOW, + opacity: float = 0.3, + stroke_width: float = 3, + ) -> Rectangle: """ Returns a unit square for the current NumberPlane. @@ -783,7 +814,7 @@ def get_unit_square( square.move_to(self.plane.coords_to_point(0, 0), DL) return square - def add_unit_square(self, animate: bool = False, **kwargs): + def add_unit_square(self, animate: bool = False, **kwargs: Any) -> Self: """ Adds a unit square to the scene via self.get_unit_square. @@ -814,8 +845,12 @@ def add_unit_square(self, animate: bool = False, **kwargs): return self def add_vector( - self, vector: Arrow | list | tuple | np.ndarray, color: str = YELLOW, **kwargs - ): + self, + vector: Arrow | list | tuple | np.ndarray, + color: ParsableManimColor = YELLOW, + animate: bool = True, + **kwargs: Any, + ) -> Arrow: """ Adds a vector to the scene, and puts it in the special list self.moving_vectors. @@ -839,11 +874,11 @@ def add_vector( Arrow The arrow representing the vector. """ - vector = super().add_vector(vector, color=color, **kwargs) + vector = super().add_vector(vector, color=color, animate=animate, **kwargs) self.moving_vectors.append(vector) return vector - def write_vector_coordinates(self, vector: Arrow, **kwargs): + def write_vector_coordinates(self, vector: Arrow, **kwargs: Any) -> Matrix: """ Returns a column matrix indicating the vector coordinates, after writing them to the screen, and adding them to the @@ -872,8 +907,8 @@ def add_transformable_label( label: MathTex | str, transformation_name: str | MathTex = "L", new_label: str | MathTex | None = None, - **kwargs, - ): + **kwargs: Any, + ) -> MathTex: """ Method for creating, and animating the addition of a transformable label for the vector. @@ -919,7 +954,7 @@ def add_title( title: str | MathTex | Tex, scale_factor: float = 1.5, animate: bool = False, - ): + ) -> Self: """ Adds a title, after scaling it, adding a background rectangle, moving it to the top and adding it to foreground_mobjects adding @@ -951,7 +986,7 @@ def add_title( self.title = title return self - def get_matrix_transformation(self, matrix: np.ndarray | list | tuple): + def get_matrix_transformation(self, matrix: np.ndarray | list | tuple) -> Any: """ Returns a function corresponding to the linear transformation represented by the matrix passed. @@ -965,7 +1000,7 @@ def get_matrix_transformation(self, matrix: np.ndarray | list | tuple): def get_transposed_matrix_transformation( self, transposed_matrix: np.ndarray | list | tuple - ): + ) -> Callable: """ Returns a function corresponding to the linear transformation represented by the transposed @@ -985,7 +1020,7 @@ def get_transposed_matrix_transformation( raise ValueError("Matrix has bad dimensions") return lambda point: np.dot(point, transposed_matrix) - def get_piece_movement(self, pieces: list | tuple | np.ndarray): + def get_piece_movement(self, pieces: list | tuple | np.ndarray) -> Transform: """ This method returns an animation that moves an arbitrary mobject in "pieces" to its corresponding .target value. @@ -1013,7 +1048,9 @@ def get_piece_movement(self, pieces: list | tuple | np.ndarray): self.add(self.ghost_vectors[-1]) return Transform(start, target, lag_ratio=0) - def get_moving_mobject_movement(self, func: Callable[[np.ndarray], np.ndarray]): + def get_moving_mobject_movement( + self, func: Callable[[np.ndarray], np.ndarray] + ) -> Animation: """ This method returns an animation that moves a mobject in "self.moving_mobjects" to its corresponding .target value. @@ -1034,11 +1071,14 @@ def get_moving_mobject_movement(self, func: Callable[[np.ndarray], np.ndarray]): for m in self.moving_mobjects: if m.target is None: m.target = m.copy() - target_point = func(m.get_center()) + temp: InternalPoint3D = m.get_center() # type: ignore[assignment] + target_point = func(temp) m.target.move_to(target_point) return self.get_piece_movement(self.moving_mobjects) - def get_vector_movement(self, func: Callable[[np.ndarray], np.ndarray]): + def get_vector_movement( + self, func: Callable[[np.ndarray], np.ndarray] + ) -> Animation: """ This method returns an animation that moves a mobject in "self.moving_vectors" to its corresponding .target value. @@ -1058,12 +1098,12 @@ def get_vector_movement(self, func: Callable[[np.ndarray], np.ndarray]): """ for v in self.moving_vectors: v.target = Vector(func(v.get_end()), color=v.get_color()) - norm = np.linalg.norm(v.target.get_end()) + norm = float(np.linalg.norm(v.target.get_end())) if norm < 0.1: v.target.get_tip().scale(norm) return self.get_piece_movement(self.moving_vectors) - def get_transformable_label_movement(self): + def get_transformable_label_movement(self) -> Animation: """ This method returns an animation that moves all labels in "self.transformable_labels" to its corresponding .target . @@ -1074,12 +1114,15 @@ def get_transformable_label_movement(self): The animation of the movement. """ for label in self.transformable_labels: + # TODO: This location and lines 933 and 335 are the only locations in + # the code where the target_text property is referenced. + target_text: MathTex | str = label.target_text label.target = self.get_vector_label( - label.vector.target, label.target_text, **label.kwargs + label.vector.target, target_text, **label.kwargs ) return self.get_piece_movement(self.transformable_labels) - def apply_matrix(self, matrix: np.ndarray | list | tuple, **kwargs): + def apply_matrix(self, matrix: np.ndarray | list | tuple, **kwargs: Any) -> None: """ Applies the transformation represented by the given matrix to the number plane, and each vector/similar @@ -1094,7 +1137,7 @@ def apply_matrix(self, matrix: np.ndarray | list | tuple, **kwargs): """ self.apply_transposed_matrix(np.array(matrix).T, **kwargs) - def apply_inverse(self, matrix: np.ndarray | list | tuple, **kwargs): + def apply_inverse(self, matrix: np.ndarray | list | tuple, **kwargs: Any) -> None: """ This method applies the linear transformation represented by the inverse of the passed matrix @@ -1110,8 +1153,8 @@ def apply_inverse(self, matrix: np.ndarray | list | tuple, **kwargs): self.apply_matrix(np.linalg.inv(matrix), **kwargs) def apply_transposed_matrix( - self, transposed_matrix: np.ndarray | list | tuple, **kwargs - ): + self, transposed_matrix: np.ndarray | list | tuple, **kwargs: Any + ) -> Any: """ Applies the transformation represented by the given transposed matrix to the number plane, @@ -1132,7 +1175,9 @@ def apply_transposed_matrix( kwargs["path_arc"] = net_rotation self.apply_function(func, **kwargs) - def apply_inverse_transpose(self, t_matrix: np.ndarray | list | tuple, **kwargs): + def apply_inverse_transpose( + self, t_matrix: np.ndarray | list | tuple, **kwargs: Any + ) -> None: """ Applies the inverse of the transformation represented by the given transposed matrix to the number plane and each @@ -1149,8 +1194,8 @@ def apply_inverse_transpose(self, t_matrix: np.ndarray | list | tuple, **kwargs) self.apply_transposed_matrix(t_inv, **kwargs) def apply_nonlinear_transformation( - self, function: Callable[[np.ndarray], np.ndarray], **kwargs - ): + self, function: Callable[[np.ndarray], np.ndarray], **kwargs: Any + ) -> None: """ Applies the non-linear transformation represented by the given function to the number plane and each @@ -1170,8 +1215,8 @@ def apply_function( self, function: Callable[[np.ndarray], np.ndarray], added_anims: list = [], - **kwargs, - ): + **kwargs: Any, + ) -> None: """ Applies the given function to each of the mobjects in self.transformable_mobjects, and plays the animation showing diff --git a/manim/scene/zoomed_scene.py b/manim/scene/zoomed_scene.py index 361c4eaf55..b865051d08 100644 --- a/manim/scene/zoomed_scene.py +++ b/manim/scene/zoomed_scene.py @@ -50,6 +50,10 @@ def construct(self): __all__ = ["ZoomedScene"] +# Note, any scenes from old videos using ZoomedScene will almost certainly +# break, as it was restructured. +from typing import TYPE_CHECKING + from ..animation.transform import ApplyMethod from ..camera.moving_camera import MovingCamera from ..camera.multi_camera import MultiCamera @@ -57,8 +61,10 @@ def construct(self): from ..mobject.types.image_mobject import ImageMobjectFromCamera from ..scene.moving_camera_scene import MovingCameraScene -# Note, any scenes from old videos using ZoomedScene will almost certainly -# break, as it was restructured. +if TYPE_CHECKING: + from typing import Any + + from manim.typing import Vector3D class ZoomedScene(MovingCameraScene): @@ -70,23 +76,23 @@ class ZoomedScene(MovingCameraScene): def __init__( self, - camera_class=MultiCamera, - zoomed_display_height=3, - zoomed_display_width=3, - zoomed_display_center=None, - zoomed_display_corner=UP + RIGHT, - zoomed_display_corner_buff=DEFAULT_MOBJECT_TO_EDGE_BUFFER, - zoomed_camera_config={ + camera_class: type[MultiCamera] = MultiCamera, + zoomed_display_height: float = 3, + zoomed_display_width: float = 3, + zoomed_display_center: Any = None, + zoomed_display_corner: Vector3D = UP + RIGHT, + zoomed_display_corner_buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER, + zoomed_camera_config: dict[str, Any] = { "default_frame_stroke_width": 2, "background_opacity": 1, }, - zoomed_camera_image_mobject_config={}, - zoomed_camera_frame_starting_position=ORIGIN, - zoom_factor=0.15, - image_frame_stroke_width=3, - zoom_activated=False, - **kwargs, - ): + zoomed_camera_image_mobject_config: dict = {}, + zoomed_camera_frame_starting_position: Vector3D = ORIGIN, + zoom_factor: float = 0.15, + image_frame_stroke_width: int = 3, + zoom_activated: bool = False, + **kwargs: Any, + ) -> None: self.zoomed_display_height = zoomed_display_height self.zoomed_display_width = zoomed_display_width self.zoomed_display_center = zoomed_display_center @@ -102,7 +108,7 @@ def __init__( self.zoom_activated = zoom_activated super().__init__(camera_class=camera_class, **kwargs) - def setup(self): + def setup(self) -> None: """ This method is used internally by Manim to setup the scene for proper use. @@ -132,7 +138,7 @@ def setup(self): self.zoomed_camera = zoomed_camera self.zoomed_display = zoomed_display - def activate_zooming(self, animate: bool = False): + def activate_zooming(self, animate: bool = False) -> None: """ This method is used to activate the zooming for the zoomed_camera. @@ -144,6 +150,7 @@ def activate_zooming(self, animate: bool = False): of the zoomed camera. """ self.zoom_activated = True + assert isinstance(self.renderer.camera, MultiCamera) self.renderer.camera.add_image_mobject_from_camera(self.zoomed_display) if animate: self.play(self.get_zoom_in_animation()) @@ -153,7 +160,7 @@ def activate_zooming(self, animate: bool = False): self.zoomed_display, ) - def get_zoom_in_animation(self, run_time: float = 2, **kwargs): + def get_zoom_in_animation(self, run_time: float = 2, **kwargs: Any) -> Any: """ Returns the animation of camera zooming in. @@ -179,7 +186,7 @@ def get_zoom_in_animation(self, run_time: float = 2, **kwargs): frame.set_stroke(width=0) return ApplyMethod(frame.restore, run_time=run_time, **kwargs) - def get_zoomed_display_pop_out_animation(self, **kwargs): + def get_zoomed_display_pop_out_animation(self, **kwargs: Any) -> Any: """ This is the animation of the popping out of the mini-display that shows the content of the zoomed @@ -195,7 +202,7 @@ def get_zoomed_display_pop_out_animation(self, **kwargs): display.replace(self.zoomed_camera.frame, stretch=True) return ApplyMethod(display.restore) - def get_zoom_factor(self): + def get_zoom_factor(self) -> float: """ Returns the Zoom factor of the Zoomed camera. Defined as the ratio between the height of the diff --git a/manim/utils/config_ops.py b/manim/utils/config_ops.py index 6e1f09990e..4767c5414c 100644 --- a/manim/utils/config_ops.py +++ b/manim/utils/config_ops.py @@ -10,11 +10,15 @@ import itertools as it +from typing import TYPE_CHECKING import numpy as np +if TYPE_CHECKING: + from typing_extensions import Any -def merge_dicts_recursively(*dicts): + +def merge_dicts_recursively(*dicts: dict[str, Any]) -> dict[str, Any]: """ Creates a dict whose keyset is the union of all the input dictionaries. The value for each key is based @@ -34,7 +38,7 @@ def merge_dicts_recursively(*dicts): return result -def update_dict_recursively(current_dict, *others): +def update_dict_recursively(current_dict, *others: dict) -> None: updated_dict = merge_dicts_recursively(current_dict, *others) current_dict.update(updated_dict) diff --git a/manim/utils/family.py b/manim/utils/family.py index 17b39f347f..64da2a52af 100644 --- a/manim/utils/family.py +++ b/manim/utils/family.py @@ -13,7 +13,7 @@ def extract_mobject_family_members( mobjects: Iterable[Mobject], use_z_index=False, only_those_with_points: bool = False, -): +) -> list[Mobject]: """Returns a list of the types of mobjects and their family members present. A "family" in this context refers to a mobject, its submobjects, and their submobjects, recursively. diff --git a/manim/utils/family_ops.py b/manim/utils/family_ops.py index 8d4af9d5a5..b3c3596524 100644 --- a/manim/utils/family_ops.py +++ b/manim/utils/family_ops.py @@ -1,6 +1,10 @@ from __future__ import annotations import itertools as it +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from manim.mobject.mobject import Mobject __all__ = [ "extract_mobject_family_members", @@ -15,7 +19,9 @@ def extract_mobject_family_members(mobject_list, only_those_with_points=False): return result -def restructure_list_to_exclude_certain_family_members(mobject_list, to_remove): +def restructure_list_to_exclude_certain_family_members( + mobject_list: list[Mobject], to_remove: list[Mobject] +): """ Removes anything in to_remove from mobject_list, but in the event that one of the items to be removed is a member of the family of an item in mobject_list, diff --git a/manim/utils/opengl.py b/manim/utils/opengl.py index d48817b76e..c7c68fd6da 100644 --- a/manim/utils/opengl.py +++ b/manim/utils/opengl.py @@ -1,10 +1,15 @@ from __future__ import annotations +from typing import TYPE_CHECKING + import numpy as np import numpy.linalg as linalg from .. import config +if TYPE_CHECKING: + import numpy.typing as npt + depth = 20 __all__ = [ @@ -72,7 +77,7 @@ def perspective_projection_matrix( return projection_matrix -def translation_matrix(x=0, y=0, z=0): +def translation_matrix(x: float = 0, y: float = 0, z: float = 0) -> npt.NDArray: return np.array( [ [1, 0, 0, x], @@ -127,7 +132,7 @@ def rotate_in_place_matrix(initial_position, x=0, y=0, z=0): ) -def rotation_matrix(x=0, y=0, z=0): +def rotation_matrix(x: float = 0, y: float = 0, z: float = 0) -> npt.NDArray: return np.matmul( np.matmul(x_rotation_matrix(x), y_rotation_matrix(y)), z_rotation_matrix(z), diff --git a/manim/utils/sounds.py b/manim/utils/sounds.py index 5e0ea060f3..903c0c8b54 100644 --- a/manim/utils/sounds.py +++ b/manim/utils/sounds.py @@ -6,13 +6,14 @@ "get_full_sound_file_path", ] +from pathlib import Path from .. import config from ..utils.file_ops import seek_full_path_from_defaults # Still in use by add_sound() function in scene_file_writer.py -def get_full_sound_file_path(sound_file_name): +def get_full_sound_file_path(sound_file_name: str) -> Path: return seek_full_path_from_defaults( sound_file_name, default_dir=config.get_dir("assets_dir"), diff --git a/mypy.ini b/mypy.ini index 12c0a1dd69..956b44ae21 100644 --- a/mypy.ini +++ b/mypy.ini @@ -70,9 +70,6 @@ ignore_errors = True [mypy-manim.mobject.*] ignore_errors = True -[mypy-manim.mobject.geometry.*] -ignore_errors = False - [mypy-manim.plugins.*] ignore_errors = True