From 94af0752c289f57e87131fa2857b22515fe7901a Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Fri, 18 Oct 2024 16:13:53 +0200 Subject: [PATCH 01/12] Removed typing errors in mobject/geometry/shape_matchers.py --- manim/mobject/geometry/shape_matchers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manim/mobject/geometry/shape_matchers.py b/manim/mobject/geometry/shape_matchers.py index 86afb58db5..0237d5531f 100644 --- a/manim/mobject/geometry/shape_matchers.py +++ b/manim/mobject/geometry/shape_matchers.py @@ -128,7 +128,7 @@ def pointwise_become_partial(self, mobject: Mobject, a: Any, b: float) -> Self: self.set_fill(opacity=b * self.original_fill_opacity) return self - def set_style(self, fill_opacity: float, **kwargs: Any) -> Self: # type: ignore[override] + def set_style(self, fill_opacity: float, **kwargs: Any) -> Self: # Unchangeable style, except for fill_opacity # All other style arguments are ignored super().set_style( From 9058b7e6752aa18bd6a140a2a4aa94337dfe7df1 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Fri, 18 Oct 2024 20:00:17 +0200 Subject: [PATCH 02/12] Added type annotations fo mobject/types/image_mobject.py and a few other files --- manim/mobject/types/image_mobject.py | 1 + 1 file changed, 1 insertion(+) diff --git a/manim/mobject/types/image_mobject.py b/manim/mobject/types/image_mobject.py index 56029f941e..4c6ee82af0 100644 --- a/manim/mobject/types/image_mobject.py +++ b/manim/mobject/types/image_mobject.py @@ -14,6 +14,7 @@ from manim.mobject.geometry.shape_matchers import SurroundingRectangle from ... import config +from ...camera.camera import Camera from ...constants import * from ...mobject.mobject import Mobject from ...utils.bezier import interpolate From 917ccd681075158067972d3f20e40def4269334d Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Fri, 18 Oct 2024 20:49:48 +0200 Subject: [PATCH 03/12] Adding type annotations to types/point_cloud_mobject.py --- manim/mobject/types/point_cloud_mobject.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manim/mobject/types/point_cloud_mobject.py b/manim/mobject/types/point_cloud_mobject.py index f5953aab8c..882de26f93 100644 --- a/manim/mobject/types/point_cloud_mobject.py +++ b/manim/mobject/types/point_cloud_mobject.py @@ -206,11 +206,11 @@ def point_from_proportion(self, alpha: float) -> Any: return self.points[np.floor(index)] @staticmethod - def get_mobject_type_class() -> type[PMobject]: + def get_mobject_type_class() -> PMobject: return PMobject # Alignment - def align_points_with_larger(self, larger_mobject: Mobject) -> None: + def align_points_with_larger(self, larger_mobject: PMobject) -> None: assert isinstance(larger_mobject, PMobject) self.apply_over_attr_arrays( lambda a: stretch_array_to_length(a, larger_mobject.get_num_points()), From 99142182dcc7af72e434617eaefe0289552d9911 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Fri, 18 Oct 2024 21:17:23 +0200 Subject: [PATCH 04/12] Fixing issues revealed by type annotations in types/point_cloud_mobject.py --- manim/mobject/types/point_cloud_mobject.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manim/mobject/types/point_cloud_mobject.py b/manim/mobject/types/point_cloud_mobject.py index 882de26f93..f5953aab8c 100644 --- a/manim/mobject/types/point_cloud_mobject.py +++ b/manim/mobject/types/point_cloud_mobject.py @@ -206,11 +206,11 @@ def point_from_proportion(self, alpha: float) -> Any: return self.points[np.floor(index)] @staticmethod - def get_mobject_type_class() -> PMobject: + def get_mobject_type_class() -> type[PMobject]: return PMobject # Alignment - def align_points_with_larger(self, larger_mobject: PMobject) -> None: + def align_points_with_larger(self, larger_mobject: Mobject) -> None: assert isinstance(larger_mobject, PMobject) self.apply_over_attr_arrays( lambda a: stretch_array_to_length(a, larger_mobject.get_num_points()), From 51ff361ddb99ec3ef054cc05b897a538d786cdec Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Fri, 18 Oct 2024 21:21:13 +0200 Subject: [PATCH 05/12] Fix issue in the CI step related to the Camera class. --- manim/mobject/types/image_mobject.py | 1 - 1 file changed, 1 deletion(-) diff --git a/manim/mobject/types/image_mobject.py b/manim/mobject/types/image_mobject.py index 4c6ee82af0..56029f941e 100644 --- a/manim/mobject/types/image_mobject.py +++ b/manim/mobject/types/image_mobject.py @@ -14,7 +14,6 @@ from manim.mobject.geometry.shape_matchers import SurroundingRectangle from ... import config -from ...camera.camera import Camera from ...constants import * from ...mobject.mobject import Mobject from ...utils.bezier import interpolate From 702612dc705cfc01e9e60e231a6b216859aaf8ce Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Fri, 18 Oct 2024 23:02:20 +0200 Subject: [PATCH 06/12] Adding type annotations to geometry/line.py --- manim/mobject/geometry/line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manim/mobject/geometry/line.py b/manim/mobject/geometry/line.py index be6c3a77ba..a12f59e628 100644 --- a/manim/mobject/geometry/line.py +++ b/manim/mobject/geometry/line.py @@ -582,7 +582,7 @@ def __init__( self.add_tip(tip_shape=tip_shape) self._set_stroke_width_from_length() - def scale(self, factor: float, scale_tips: bool = False, **kwargs: Any) -> Self: # type: ignore[override] + def scale(self, factor: float, scale_tips: bool = False, **kwargs: Any) -> Self: r"""Scale an arrow, but keep stroke width and arrow tip size fixed. From 0e8b51d84e4b09815f3832cdd9d97aafad3b465a Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Sat, 19 Oct 2024 21:00:23 +0200 Subject: [PATCH 07/12] Ignore type errors related to super and sub class in line.py and shape_matchers.py --- manim/mobject/geometry/line.py | 2 +- manim/mobject/geometry/shape_matchers.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manim/mobject/geometry/line.py b/manim/mobject/geometry/line.py index a12f59e628..be6c3a77ba 100644 --- a/manim/mobject/geometry/line.py +++ b/manim/mobject/geometry/line.py @@ -582,7 +582,7 @@ def __init__( self.add_tip(tip_shape=tip_shape) self._set_stroke_width_from_length() - def scale(self, factor: float, scale_tips: bool = False, **kwargs: Any) -> Self: + def scale(self, factor: float, scale_tips: bool = False, **kwargs: Any) -> Self: # type: ignore[override] r"""Scale an arrow, but keep stroke width and arrow tip size fixed. diff --git a/manim/mobject/geometry/shape_matchers.py b/manim/mobject/geometry/shape_matchers.py index 0237d5531f..86afb58db5 100644 --- a/manim/mobject/geometry/shape_matchers.py +++ b/manim/mobject/geometry/shape_matchers.py @@ -128,7 +128,7 @@ def pointwise_become_partial(self, mobject: Mobject, a: Any, b: float) -> Self: self.set_fill(opacity=b * self.original_fill_opacity) return self - def set_style(self, fill_opacity: float, **kwargs: Any) -> Self: + def set_style(self, fill_opacity: float, **kwargs: Any) -> Self: # type: ignore[override] # Unchangeable style, except for fill_opacity # All other style arguments are ignored super().set_style( From 11fa57e23c728ca1c87cde864b88cd7239ff856f Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Sat, 19 Oct 2024 21:53:41 +0200 Subject: [PATCH 08/12] Dealing with some issues and ignoring others identified by type checking --- manim/mobject/geometry/boolean_ops.py | 4 ++-- manim/mobject/geometry/line.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/manim/mobject/geometry/boolean_ops.py b/manim/mobject/geometry/boolean_ops.py index f02b4f7be6..d3de0d29e6 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 be6c3a77ba..c8f34ddeee 100644 --- a/manim/mobject/geometry/line.py +++ b/manim/mobject/geometry/line.py @@ -169,9 +169,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: @@ -333,7 +333,7 @@ def get_start(self) -> Point3D: 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() @@ -348,7 +348,7 @@ def get_end(self) -> Point3D: 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() From 73639131f3f38e1d0680c8cddb78487e9c4502cf Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Sun, 20 Oct 2024 20:16:27 +0200 Subject: [PATCH 09/12] Ignored a number of type issues related to float and floating[Any]. --- manim/mobject/geometry/arc.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/manim/mobject/geometry/arc.py b/manim/mobject/geometry/arc.py index 2923239944..75f83d00e8 100644 --- a/manim/mobject/geometry/arc.py +++ b/manim/mobject/geometry/arc.py @@ -284,15 +284,15 @@ def get_last_handle(self) -> Point3D: def get_end(self) -> Point3D: 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) -> Point3D: 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() From 68a4280a3aa4bf7b7893b7830d3ffbd9cf7b8d72 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Tue, 21 Jan 2025 12:13:25 +0100 Subject: [PATCH 10/12] Help mypy to determine types. # Conflicts: # manim/mobject/geometry/arc.py --- manim/mobject/geometry/arc.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/manim/mobject/geometry/arc.py b/manim/mobject/geometry/arc.py index 75f83d00e8..38673fbb77 100644 --- a/manim/mobject/geometry/arc.py +++ b/manim/mobject/geometry/arc.py @@ -326,10 +326,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: Point3D = np.asarray(arc_center) - self.start_angle = start_angle - self.angle = angle + self.num_components: int = num_components + self.arc_center: Point3D = arc_center + self.start_angle: float = start_angle + self.angle: float = angle self._failed_to_get_center: bool = False super().__init__(**kwargs) From f9d905fed35249829826b37b73db14d2622cd671 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Tue, 21 Jan 2025 12:16:10 +0100 Subject: [PATCH 11/12] More work on addressing typing issues. # Conflicts: # manim/mobject/geometry/polygram.py --- manim/mobject/geometry/polygram.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/manim/mobject/geometry/polygram.py b/manim/mobject/geometry/polygram.py index 7baf6f183c..82419011a1 100644 --- a/manim/mobject/geometry/polygram.py +++ b/manim/mobject/geometry/polygram.py @@ -325,8 +325,8 @@ def construct(self): self.add(isosceles, square_and_triangles) """ - def __init__(self, *vertices: Point3DLike, **kwargs: Any) -> None: - super().__init__(vertices, **kwargs) + def __init__(self, *vertices: Point3D, **kwargs: Any) -> None: + super().__init__(np.array(vertices), **kwargs) class RegularPolygram(Polygram): @@ -417,7 +417,7 @@ def gen_polygon_vertices(start_angle: float | None) -> tuple[list[Any], float]: vertex_groups.append(group) - super().__init__(*vertex_groups, **kwargs) + super().__init__(np.array(*vertex_groups), **kwargs) class RegularPolygon(RegularPolygram): From 462822699ad7b5b91c395f2c8015faaa637fba6a Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Tue, 21 Jan 2025 12:16:48 +0100 Subject: [PATCH 12/12] Reverting two changes that triggers an error in the automatic testing. Further work on type hinting. Avoid forwarding positional arguments from Arrow to Line in the constructor. Revert "Avoid forwarding positional arguments from Arrow to Line in the constructor." This reverts commit 80ae8576c164f394c3c3c82dc0c30336b72e53a3. Removed several type ignore statements and addressed comments from JasonGrace2282 Revert "Activate mypy check of mobject.geometry.*" This reverts commit d477c9a99493a85f683a5ee0cb6098835bc6b42e. Revert "Removed several type ignore statements and addressed comments from JasonGrace2282" This reverts commit 07bbe3f2200d42fe1a575ad7362163538a07ec84. Added type annotations to zoomed_scene.py Error count: 308 -> 303 Adding type annotations to all methods in vector_space_scene.py Error count: 303 -> 272 Get rid of no-untyped-call errors from my in the vector_space_scene.py file Error count: 272 -> 343 Handle type issues related to ManimColor in vector_space_scene.py Handle var-annotated issues in vector_space_scene.py Error count: 332 -> 330 Handling has-type type errors in vector_space_scene.py Error count: 330 -> 285 Handled name-defined type issues in vector_space_scene.py Error count: 285 -> 282 Address type issue with calling an untyped method. Error count: 282 -> 281 Fix some typing issues in transform_mathcing_parts.py Change stroke_width to float in vector_space_scene.py Handled a few type errors. Error count: 267 Handled several typing issues in three_d_scene.py Error count: 267 -> 248 Dealing with type errors in scene_file_writer.py Error count: 248 -> 216 Ensured that all methods in scene.py have type declarations. Error count: 216 -> 225 Handle type issues related to interactivity by asserting that the camera is the OpenGLCamera Error count: 225 -> 182 Handle type issues in scene.py Error count: 182 -> 167 Asserting that the renderer or camera is of the proper type to use certain methods. This is mainly related to interactive elements and the 3D camera used in the ThreeDScene Error count: 167 -> 143 Avoid cyclic import of dependencies Error count: 143 -> 143 Handling no-untyped-call type errors in manim/scene/scene.py Error count: 143 -> 131 Handling assignment type errors in manim/scene/*.py Error count: 131 -> 121 Handling arg-type type errors in manim/scene/*.py Error count: 121 -> 116 Handling arg-type type errors in manim/scene/*.py Error count: 116 -> 112 Fixing various type errors Error count: 112 -> 102 Fixing various type errors Error count: 102 -> 97 Fixing various type errors Error count: 97 -> 90 Some aggressive changes to silence a significant number of type errors. Error count: 90 -> 66 Commented out an import (IPython) that makes the CI tests fail. Fix various type errors. More type annotations. Code cleanup. Remove the property mobject_updater_lists of the Scene class as it is not used anywhere. Handle import-untyped typing issues. --- manim/animation/transform.py | 8 +- manim/animation/transform_matching_parts.py | 12 +- manim/camera/camera.py | 4 +- manim/camera/moving_camera.py | 21 +- manim/camera/multi_camera.py | 6 +- manim/camera/three_d_camera.py | 2 +- manim/gui/gui.py | 6 +- manim/mobject/geometry/arc.py | 8 +- manim/mobject/geometry/boolean_ops.py | 4 +- manim/mobject/geometry/line.py | 8 +- manim/mobject/geometry/polygram.py | 4 +- manim/mobject/matrix.py | 6 +- manim/mobject/text/tex_mobject.py | 20 +- manim/mobject/value_tracker.py | 9 +- manim/renderer/cairo_renderer.py | 20 +- manim/renderer/opengl_renderer.py | 38 ++- manim/renderer/shader.py | 9 +- manim/scene/moving_camera_scene.py | 13 +- manim/scene/scene.py | 290 ++++++++++++-------- manim/scene/scene_file_writer.py | 54 ++-- manim/scene/section.py | 2 +- manim/scene/three_d_scene.py | 112 +++++--- manim/scene/vector_space_scene.py | 211 ++++++++------ manim/scene/zoomed_scene.py | 49 ++-- manim/utils/config_ops.py | 7 +- manim/utils/family_ops.py | 4 + manim/utils/opengl.py | 3 + mypy.ini | 3 + 28 files changed, 575 insertions(+), 358 deletions(-) diff --git a/manim/animation/transform.py b/manim/animation/transform.py index aff109ec71..42b81880a2 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 38673fbb77..fc886026fa 100644 --- a/manim/mobject/geometry/arc.py +++ b/manim/mobject/geometry/arc.py @@ -284,15 +284,15 @@ def get_last_handle(self) -> Point3D: def get_end(self) -> Point3D: if self.has_tip(): - return self.tip.get_start() # type: ignore[return-value] + return self.tip.get_start() else: - return super().get_end() # type: ignore[return-value] + return super().get_end() def get_start(self) -> Point3D: if self.has_start_tip(): - return self.start_tip.get_start() # type: ignore[return-value] + return self.start_tip.get_start() else: - return super().get_start() # type: ignore[return-value] + return super().get_start() def get_length(self) -> float: start, end = self.get_start_and_end() diff --git a/manim/mobject/geometry/boolean_ops.py b/manim/mobject/geometry/boolean_ops.py index d3de0d29e6..f02b4f7be6 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 # type: ignore[import-untyped] +from pathops import Path as SkiaPath 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]): # type: ignore[arg-type] + if vmobject.consider_points_equals_2d(subpath[0], subpath[-1]): path.close() return path diff --git a/manim/mobject/geometry/line.py b/manim/mobject/geometry/line.py index c8f34ddeee..be6c3a77ba 100644 --- a/manim/mobject/geometry/line.py +++ b/manim/mobject/geometry/line.py @@ -169,9 +169,9 @@ def _pointify( if isinstance(mob_or_point, (Mobject, OpenGLMobject)): mob = mob_or_point if direction is None: - return mob.get_center() # type: ignore[return-value] + return mob.get_center() else: - return mob.get_boundary_point(direction) # type: ignore[return-value] + return mob.get_boundary_point(direction) return np.array(mob_or_point) def set_path_arc(self, new_value: float) -> None: @@ -333,7 +333,7 @@ def get_start(self) -> Point3D: array([-1., 0., 0.]) """ if len(self.submobjects) > 0: - return self.submobjects[0].get_start() # type: ignore[return-value] + return self.submobjects[0].get_start() else: return super().get_start() @@ -348,7 +348,7 @@ def get_end(self) -> Point3D: array([1., 0., 0.]) """ if len(self.submobjects) > 0: - return self.submobjects[-1].get_end() # type: ignore[return-value] + return self.submobjects[-1].get_end() else: return super().get_end() diff --git a/manim/mobject/geometry/polygram.py b/manim/mobject/geometry/polygram.py index 82419011a1..be84828042 100644 --- a/manim/mobject/geometry/polygram.py +++ b/manim/mobject/geometry/polygram.py @@ -326,7 +326,7 @@ def construct(self): """ def __init__(self, *vertices: Point3D, **kwargs: Any) -> None: - super().__init__(np.array(vertices), **kwargs) + super().__init__(vertices, **kwargs) class RegularPolygram(Polygram): @@ -417,7 +417,7 @@ def gen_polygon_vertices(start_angle: float | None) -> tuple[list[Any], float]: vertex_groups.append(group) - super().__init__(np.array(*vertex_groups), **kwargs) + super().__init__(*vertex_groups, **kwargs) class RegularPolygon(RegularPolygram): diff --git a/manim/mobject/matrix.py b/manim/mobject/matrix.py index 673aba1877..03ca8bdcaa 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 7efd7b022f..8b1c2b09ac 100644 --- a/manim/renderer/cairo_renderer.py +++ b/manim/renderer/cairo_renderer.py @@ -33,11 +33,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 +51,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 +119,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 @@ -263,7 +263,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 7d4b6a4467..2eae481056 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): @@ -440,11 +448,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 fc3d3ede54..e009556a98 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 @@ -52,20 +52,23 @@ from ..utils.iterables import list_difference_update, list_update if TYPE_CHECKING: - from collections.abc import Sequence - from typing import Callable + from collections.abc import Iterable, Sequence + from types import FrameType + from typing import Any, Callable - from manim.mobject.mobject import _AnimationBuilder + 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", [], {})) @@ -113,24 +116,30 @@ def __init__( 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: @@ -141,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, ) @@ -149,15 +160,15 @@ 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 @property @@ -165,7 +176,7 @@ def time(self) -> float: """The time since the start of the scene.""" return self.renderer.time - def __deepcopy__(self, clone_from_id): + def __deepcopy__(self, clone_from_id: Any) -> Scene: cls = self.__class__ result = cls.__new__(cls) clone_from_id[id(self)] = result @@ -175,7 +186,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: @@ -219,11 +229,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. @@ -239,6 +247,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 @@ -263,7 +272,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 @@ -271,7 +282,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 @@ -279,7 +290,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 @@ -324,10 +335,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. @@ -343,7 +354,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. @@ -355,12 +366,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, @@ -392,7 +403,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 @@ -406,7 +419,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. @@ -419,13 +432,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, @@ -448,7 +461,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. @@ -466,7 +479,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) @@ -477,15 +490,18 @@ 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: list[Animation]) -> None: @@ -500,7 +516,7 @@ def add_mobjects_from_animations(self, animations: list[Animation]) -> None: 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 @@ -513,7 +529,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) @@ -523,8 +539,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: @@ -630,7 +650,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, @@ -668,7 +688,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. @@ -687,9 +709,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 @@ -703,7 +727,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. @@ -722,7 +746,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. @@ -739,7 +763,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. @@ -757,7 +781,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. @@ -774,7 +798,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. @@ -793,7 +817,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. @@ -813,7 +837,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. @@ -829,7 +853,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). @@ -860,7 +884,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, @@ -880,9 +906,12 @@ def get_moving_and_static_mobjects(self, animations): def compile_animations( self, - *args: Animation | Mobject | _AnimationBuilder, - **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(). @@ -924,7 +953,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. @@ -977,10 +1006,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. @@ -1008,7 +1037,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) @@ -1051,7 +1080,7 @@ def validate_run_time( return run_time - 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. @@ -1072,12 +1101,14 @@ def get_run_time(self, animations: list[Animation]): def play( self, - *args: Animation | Mobject | _AnimationBuilder, - 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 @@ -1142,7 +1173,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 @@ -1173,7 +1204,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`` @@ -1191,7 +1222,9 @@ def pause(self, duration: float = DEFAULT_WAIT_TIME): duration = self.validate_run_time(duration, self.pause, "duration") 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 @@ -1207,9 +1240,11 @@ def wait_until(self, stop_condition: Callable[[], bool], max_time: float = 60): def compile_animation_data( self, - *animations: Animation | Mobject | _AnimationBuilder, - **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. @@ -1275,7 +1310,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, @@ -1304,11 +1339,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: @@ -1333,23 +1370,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() @@ -1358,7 +1398,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 @@ -1375,11 +1415,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( @@ -1406,7 +1446,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) @@ -1483,7 +1528,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 @@ -1497,7 +1543,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( @@ -1507,7 +1553,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", @@ -1525,7 +1573,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: @@ -1589,8 +1637,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. @@ -1631,7 +1679,8 @@ def construct(self): time = self.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 @@ -1641,13 +1690,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: @@ -1663,10 +1714,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]) @@ -1680,7 +1738,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 @@ -1688,7 +1749,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. @@ -1761,9 +1829,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 d256afb736..fcdf06e534 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 @@ -231,7 +231,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. @@ -256,7 +256,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. @@ -286,11 +286,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() @@ -299,7 +299,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. @@ -344,8 +344,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. @@ -412,7 +412,7 @@ def end_animation(self, allow_write: bool = False) -> None: 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() @@ -439,7 +439,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. @@ -462,7 +462,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) @@ -476,14 +476,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. @@ -524,7 +526,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 @@ -554,7 +556,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, @@ -590,7 +592,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 @@ -615,9 +617,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", @@ -644,7 +646,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, ) @@ -716,7 +718,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. @@ -814,7 +816,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. @@ -836,7 +838,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) @@ -858,7 +860,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 @@ -872,7 +874,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 @@ -880,7 +882,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 401144d55f..8b83241074 100644 --- a/manim/utils/config_ops.py +++ b/manim/utils/config_ops.py @@ -10,12 +10,15 @@ import itertools as it -from typing import Any +from typing import TYPE_CHECKING import numpy.typing as npt +if TYPE_CHECKING: + from typing_extensions import Any -def merge_dicts_recursively(*dicts: dict[Any, Any]) -> dict[Any, Any]: + +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 diff --git a/manim/utils/family_ops.py b/manim/utils/family_ops.py index e7cf011161..b9446c4d0a 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 from manim.mobject.mobject import Mobject diff --git a/manim/utils/opengl.py b/manim/utils/opengl.py index 877cbc2e8f..f70256575d 100644 --- a/manim/utils/opengl.py +++ b/manim/utils/opengl.py @@ -21,6 +21,9 @@ from manim.typing import MatrixMN +if TYPE_CHECKING: + import numpy.typing as npt + depth = 20 __all__ = [ diff --git a/mypy.ini b/mypy.ini index 80571869be..d7c8deb141 100644 --- a/mypy.ini +++ b/mypy.ini @@ -74,6 +74,9 @@ ignore_errors = True ignore_errors = False [mypy-manim.mobject.geometry.*] +ignore_errors = False + +[mypy-manim.plugins.*] ignore_errors = True [mypy-manim.renderer.*]