From 9e4fdd4e36eed030b2a84d5725a26674091f10b6 Mon Sep 17 00:00:00 2001 From: godalming Date: Fri, 25 Apr 2025 18:48:28 +0100 Subject: [PATCH 1/5] Fix type errors, and add typings for the transformation functions --- manim/mobject/geometry/arc.py | 2 +- manim/mobject/geometry/boolean_ops.py | 8 +-- manim/mobject/graphing/scale.py | 2 +- manim/mobject/mobject.py | 73 +++++++++++++++++++-------- manim/utils/bezier.py | 10 ++-- manim/utils/commands.py | 2 + manim/utils/images.py | 6 +-- manim/utils/space_ops.py | 4 +- mypy.ini | 2 +- 9 files changed, 71 insertions(+), 38 deletions(-) diff --git a/manim/mobject/geometry/arc.py b/manim/mobject/geometry/arc.py index 2923239944..b01dd7ce8d 100644 --- a/manim/mobject/geometry/arc.py +++ b/manim/mobject/geometry/arc.py @@ -183,7 +183,7 @@ def position_tip(self, tip: tips.ArrowTip, at_start: bool = False) -> tips.Arrow else: handle = self.get_last_handle() anchor = self.get_end() - angles = cartesian_to_spherical((handle - anchor).tolist()) + angles = cartesian_to_spherical(handle - anchor) tip.rotate( angles[1] - PI - tip.tip_angle, ) # Rotates the tip along the azimuthal diff --git a/manim/mobject/geometry/boolean_ops.py b/manim/mobject/geometry/boolean_ops.py index f02b4f7be6..8de14d348f 100644 --- a/manim/mobject/geometry/boolean_ops.py +++ b/manim/mobject/geometry/boolean_ops.py @@ -59,7 +59,7 @@ def _convert_2d_to_3d_array( list_of_points = list(points) for i, point in enumerate(list_of_points): if len(point) == 2: - list_of_points[i] = np.array(list(point) + [z_dim]) + list_of_points[i] = np.append(point, z_dim) return np.asarray(list_of_points) def _convert_vmobject_to_skia_path(self, vmobject: VMobject) -> SkiaPath: @@ -78,10 +78,10 @@ def _convert_vmobject_to_skia_path(self, vmobject: VMobject) -> SkiaPath: """ path = SkiaPath() - if not np.all(np.isfinite(vmobject.points)): - points = np.zeros((1, 3)) # point invalid? - else: + if np.all(np.isfinite(vmobject.points)): points = vmobject.points + else: + points = np.zeros((1, 3)) # point invalid? if len(points) == 0: # what? No points so return empty path return path diff --git a/manim/mobject/graphing/scale.py b/manim/mobject/graphing/scale.py index 78ffa2308b..5f9484b7f6 100644 --- a/manim/mobject/graphing/scale.py +++ b/manim/mobject/graphing/scale.py @@ -163,7 +163,7 @@ def get_custom_labels( self, val_range: Iterable[float], unit_decimal_places: int = 0, - **base_config: dict[str, Any], + **base_config: Any, ) -> list[Mobject]: """Produces custom :class:`~.Integer` labels in the form of ``10^2``. diff --git a/manim/mobject/mobject.py b/manim/mobject/mobject.py index 6ad3ece777..1087ba01f1 100644 --- a/manim/mobject/mobject.py +++ b/manim/mobject/mobject.py @@ -51,6 +51,7 @@ PathFuncType, PixelArray, Point3D, + Point3D_Array, Point3DLike, Point3DLike_Array, Vector3D, @@ -1225,7 +1226,12 @@ def shift(self, *vectors: Vector3D) -> Self: return self - def scale(self, scale_factor: float, **kwargs) -> Self: + def scale( + self, + scale_factor: float, + about_point: Point3DLike | None = None, + about_edge: Vector3D | None = None, + ) -> Self: r"""Scale the size by a factor. Default behavior is to scale about the center of the mobject. @@ -1236,9 +1242,10 @@ def scale(self, scale_factor: float, **kwargs) -> Self: The scaling factor :math:`\alpha`. If :math:`0 < |\alpha| < 1`, the mobject will shrink, and for :math:`|\alpha| > 1` it will grow. Furthermore, if :math:`\alpha < 0`, the mobject is also flipped. - kwargs - Additional keyword arguments passed to - :meth:`apply_points_function_about_point`. + about_point + The point about which to apply the scaling. + about_edge + The edge about which to apply the scaling. Returns ------- @@ -1267,7 +1274,7 @@ def construct(self): """ self.apply_points_function_about_point( - lambda points: scale_factor * points, **kwargs + lambda points: scale_factor * points, about_point, about_edge ) return self @@ -1280,16 +1287,21 @@ def rotate( angle: float, axis: Vector3D = OUT, about_point: Point3DLike | None = None, - **kwargs, + about_edge: Vector3D | None = None, ) -> Self: """Rotates the :class:`~.Mobject` about a certain point.""" rot_matrix = rotation_matrix(angle, axis) self.apply_points_function_about_point( - lambda points: np.dot(points, rot_matrix.T), about_point, **kwargs + lambda points: np.dot(points, rot_matrix.T), about_point, about_edge ) return self - def flip(self, axis: Vector3D = UP, **kwargs) -> Self: + def flip( + self, + axis: Vector3D = UP, + about_point: Point3DLike | None = None, + about_edge: Vector3D | None = None, + ) -> Self: """Flips/Mirrors an mobject about its center. Examples @@ -1306,26 +1318,37 @@ def construct(self): self.add(s2) """ - return self.rotate(TAU / 2, axis, **kwargs) + return self.rotate(TAU / 2, axis, about_point, about_edge) - def stretch(self, factor: float, dim: int, **kwargs) -> Self: + def stretch( + self, + factor: float, + dim: int, + about_point: Point3DLike | None = None, + about_edge: Vector3D | None = None, + ) -> Self: def func(points: Point3D_Array) -> Point3D_Array: points[:, dim] *= factor return points - self.apply_points_function_about_point(func, **kwargs) + self.apply_points_function_about_point(func, about_point, about_edge) return self - def apply_function(self, function: MappingFunction, **kwargs) -> Self: + def apply_function( + self, + function: MappingFunction, + about_point: Point3DLike | None = None, + about_edge: Vector3D | None = None, + ) -> Self: # Default to applying matrix about the origin, not mobjects center - if len(kwargs) == 0: - kwargs["about_point"] = ORIGIN + if about_point == None and about_edge == None: + about_point = ORIGIN def multi_mapping_function(points: Point3D_Array) -> Point3D_Array: result: Point3D_Array = np.apply_along_axis(function, 1, points) return result - self.apply_points_function_about_point(multi_mapping_function, **kwargs) + self.apply_points_function_about_point(multi_mapping_function, about_point, about_edge) return self def apply_function_to_position(self, function: MappingFunction) -> Self: @@ -1337,20 +1360,28 @@ def apply_function_to_submobject_positions(self, function: MappingFunction) -> S submob.apply_function_to_position(function) return self - def apply_matrix(self, matrix, **kwargs) -> Self: + def apply_matrix( + self, + matrix, + about_point: Point3DLike | None = None, + about_edge: Vector3D | None = None, + ) -> Self: # Default to applying matrix about the origin, not mobjects center - if ("about_point" not in kwargs) and ("about_edge" not in kwargs): - kwargs["about_point"] = ORIGIN + if about_point == None and about_edge == None: + about_point = ORIGIN full_matrix = np.identity(self.dim) matrix = np.array(matrix) full_matrix[: matrix.shape[0], : matrix.shape[1]] = matrix self.apply_points_function_about_point( - lambda points: np.dot(points, full_matrix.T), **kwargs + lambda points: np.dot(points, full_matrix.T), about_point, about_edge ) return self def apply_complex_function( - self, function: Callable[[complex], complex], **kwargs + self, + function: Callable[[complex], complex], + about_point: Point3DLike | None = None, + about_edge: Vector3D | None = None, ) -> Self: """Applies a complex function to a :class:`Mobject`. The x and y Point3Ds correspond to the real and imaginary parts respectively. @@ -1383,7 +1414,7 @@ def R3_func(point): xy_complex = function(complex(x, y)) return [xy_complex.real, xy_complex.imag, z] - return self.apply_function(R3_func) + return self.apply_function(R3_func, about_point, about_edge) def reverse_points(self) -> Self: for mob in self.family_members_with_points(): diff --git a/manim/utils/bezier.py b/manim/utils/bezier.py index 28958309a5..060164fd12 100644 --- a/manim/utils/bezier.py +++ b/manim/utils/bezier.py @@ -915,10 +915,10 @@ def subdivide_bezier(points: BezierPointsLike, n_divisions: int) -> Spline: :class:`~.Spline` An array containing the points defining the new :math:`n` subcurves. """ + points = np.asarray(points) if n_divisions == 1: return points - points = np.asarray(points) N, dim = points.shape if N <= 4: @@ -1754,10 +1754,10 @@ def get_quadratic_approximation_of_cubic( def get_quadratic_approximation_of_cubic( - a0: Point3D | Point3D_Array, - h0: Point3D | Point3D_Array, - h1: Point3D | Point3D_Array, - a1: Point3D | Point3D_Array, + a0: Point3DLike | Point3DLike_Array, + h0: Point3DLike | Point3DLike_Array, + h1: Point3DLike | Point3DLike_Array, + a1: Point3DLike | Point3DLike_Array, ) -> QuadraticSpline | QuadraticBezierPath: r"""If ``a0``, ``h0``, ``h1`` and ``a1`` are the control points of a cubic Bézier curve, approximate the curve with two quadratic Bézier curves and diff --git a/manim/utils/commands.py b/manim/utils/commands.py index b8154ca8be..4a114d133e 100644 --- a/manim/utils/commands.py +++ b/manim/utils/commands.py @@ -64,6 +64,8 @@ def get_video_metadata(path_to_video: str | os.PathLike) -> VideoMetadata: "pix_fmt": stream.codec_context.pix_fmt, } + raise Exception("Unreachable") + def get_dir_layout(dirpath: Path) -> Generator[str, None, None]: """Get list of paths relative to dirpath of all files in dir and subdirs recursively.""" diff --git a/manim/utils/images.py b/manim/utils/images.py index dd5c57858e..9d96edab09 100644 --- a/manim/utils/images.py +++ b/manim/utils/images.py @@ -40,16 +40,16 @@ def get_full_vector_image_path(image_file_name: str | PurePath) -> Path: ) -def drag_pixels(frames: list[np.array]) -> list[np.array]: +def drag_pixels(frames: list[np.ndarray]) -> list[np.ndarray]: curr = frames[0] - new_frames = [] + new_frames: list[np.ndarray] = [] for frame in frames: curr += (curr == 0) * np.array(frame) new_frames.append(np.array(curr)) return new_frames -def invert_image(image: np.array) -> Image: +def invert_image(image: np.ndarray) -> Image.Image: arr = np.array(image) arr = (255 * np.ones(arr.shape)).astype(arr.dtype) - arr return Image.fromarray(arr) diff --git a/manim/utils/space_ops.py b/manim/utils/space_ops.py index 963c0811ee..105b54d77f 100644 --- a/manim/utils/space_ops.py +++ b/manim/utils/space_ops.py @@ -802,14 +802,14 @@ def earclip_triangulation(verts: np.ndarray, ring_ends: list) -> list: return [indices[mi] for mi in meta_indices] -def cartesian_to_spherical(vec: Sequence[float]) -> np.ndarray: +def cartesian_to_spherical(vec: np.ndarray | Sequence[float]) -> np.ndarray | Sequence[float]: """Returns an array of numbers corresponding to each polar coordinate value (distance, phi, theta). Parameters ---------- vec - A numpy array ``[x, y, z]``. + A numpy array or a sequence of floats ``[x, y, z]``. """ norm = np.linalg.norm(vec) if norm == 0: diff --git a/mypy.ini b/mypy.ini index 80571869be..6c5e05cb90 100644 --- a/mypy.ini +++ b/mypy.ini @@ -74,7 +74,7 @@ ignore_errors = True ignore_errors = False [mypy-manim.mobject.geometry.*] -ignore_errors = True +ignore_errors = False [mypy-manim.renderer.*] ignore_errors = True From 2cbcbb29f2b02772fcd351448ad2e2717ba25dfe Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 18:23:48 +0000 Subject: [PATCH 2/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- manim/mobject/mobject.py | 4 +++- manim/utils/space_ops.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/manim/mobject/mobject.py b/manim/mobject/mobject.py index 1087ba01f1..8764b0609b 100644 --- a/manim/mobject/mobject.py +++ b/manim/mobject/mobject.py @@ -1348,7 +1348,9 @@ def multi_mapping_function(points: Point3D_Array) -> Point3D_Array: result: Point3D_Array = np.apply_along_axis(function, 1, points) return result - self.apply_points_function_about_point(multi_mapping_function, about_point, about_edge) + self.apply_points_function_about_point( + multi_mapping_function, about_point, about_edge + ) return self def apply_function_to_position(self, function: MappingFunction) -> Self: diff --git a/manim/utils/space_ops.py b/manim/utils/space_ops.py index 105b54d77f..6036ee28d1 100644 --- a/manim/utils/space_ops.py +++ b/manim/utils/space_ops.py @@ -802,7 +802,9 @@ def earclip_triangulation(verts: np.ndarray, ring_ends: list) -> list: return [indices[mi] for mi in meta_indices] -def cartesian_to_spherical(vec: np.ndarray | Sequence[float]) -> np.ndarray | Sequence[float]: +def cartesian_to_spherical( + vec: np.ndarray | Sequence[float], +) -> np.ndarray | Sequence[float]: """Returns an array of numbers corresponding to each polar coordinate value (distance, phi, theta). From 2f5c8b6ffc072927ec5923026022a4e7d559ef3f Mon Sep 17 00:00:00 2001 From: godalming Date: Fri, 25 Apr 2025 19:42:46 +0100 Subject: [PATCH 3/5] Fix CI errors --- manim/mobject/mobject.py | 4 ++-- manim/mobject/types/vectorized_mobject.py | 4 ++-- manim/utils/commands.py | 2 -- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/manim/mobject/mobject.py b/manim/mobject/mobject.py index 8764b0609b..e68462eafa 100644 --- a/manim/mobject/mobject.py +++ b/manim/mobject/mobject.py @@ -1341,7 +1341,7 @@ def apply_function( about_edge: Vector3D | None = None, ) -> Self: # Default to applying matrix about the origin, not mobjects center - if about_point == None and about_edge == None: + if about_point is None and about_edge is None: about_point = ORIGIN def multi_mapping_function(points: Point3D_Array) -> Point3D_Array: @@ -1369,7 +1369,7 @@ def apply_matrix( about_edge: Vector3D | None = None, ) -> Self: # Default to applying matrix about the origin, not mobjects center - if about_point == None and about_edge == None: + if about_point is None and about_edge is None: about_point = ORIGIN full_matrix = np.identity(self.dim) matrix = np.array(matrix) diff --git a/manim/mobject/types/vectorized_mobject.py b/manim/mobject/types/vectorized_mobject.py index 321fe4287b..c967306c5c 100644 --- a/manim/mobject/types/vectorized_mobject.py +++ b/manim/mobject/types/vectorized_mobject.py @@ -1192,10 +1192,10 @@ def rotate( angle: float, axis: Vector3D = OUT, about_point: Point3DLike | None = None, - **kwargs, + about_edge: Vector3D | None = None, ) -> Self: self.rotate_sheen_direction(angle, axis) - super().rotate(angle, axis, about_point, **kwargs) + super().rotate(angle, axis, about_point, about_edge) return self def scale_handle_to_anchor_distances(self, factor: float) -> Self: diff --git a/manim/utils/commands.py b/manim/utils/commands.py index 4a114d133e..b8154ca8be 100644 --- a/manim/utils/commands.py +++ b/manim/utils/commands.py @@ -64,8 +64,6 @@ def get_video_metadata(path_to_video: str | os.PathLike) -> VideoMetadata: "pix_fmt": stream.codec_context.pix_fmt, } - raise Exception("Unreachable") - def get_dir_layout(dirpath: Path) -> Generator[str, None, None]: """Get list of paths relative to dirpath of all files in dir and subdirs recursively.""" From 2e2781026e7d0eea2a4d2c511d4974bfd53c95f3 Mon Sep 17 00:00:00 2001 From: godalming Date: Fri, 25 Apr 2025 20:54:47 +0100 Subject: [PATCH 4/5] Fix CI errors again --- manim/mobject/mobject.py | 11 +++++++++-- manim/mobject/types/vectorized_mobject.py | 22 ++++++++++++++++++---- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/manim/mobject/mobject.py b/manim/mobject/mobject.py index e68462eafa..c114dcd113 100644 --- a/manim/mobject/mobject.py +++ b/manim/mobject/mobject.py @@ -1229,6 +1229,7 @@ def shift(self, *vectors: Vector3D) -> Self: def scale( self, scale_factor: float, + *, about_point: Point3DLike | None = None, about_edge: Vector3D | None = None, ) -> Self: @@ -1287,6 +1288,7 @@ def rotate( angle: float, axis: Vector3D = OUT, about_point: Point3DLike | None = None, + *, about_edge: Vector3D | None = None, ) -> Self: """Rotates the :class:`~.Mobject` about a certain point.""" @@ -1299,6 +1301,7 @@ def rotate( def flip( self, axis: Vector3D = UP, + *, about_point: Point3DLike | None = None, about_edge: Vector3D | None = None, ) -> Self: @@ -1318,12 +1321,13 @@ def construct(self): self.add(s2) """ - return self.rotate(TAU / 2, axis, about_point, about_edge) + return self.rotate(TAU / 2, axis, about_point=about_point, about_edge=about_edge) def stretch( self, factor: float, dim: int, + *, about_point: Point3DLike | None = None, about_edge: Vector3D | None = None, ) -> Self: @@ -1337,6 +1341,7 @@ def func(points: Point3D_Array) -> Point3D_Array: def apply_function( self, function: MappingFunction, + *, about_point: Point3DLike | None = None, about_edge: Vector3D | None = None, ) -> Self: @@ -1365,6 +1370,7 @@ def apply_function_to_submobject_positions(self, function: MappingFunction) -> S def apply_matrix( self, matrix, + *, about_point: Point3DLike | None = None, about_edge: Vector3D | None = None, ) -> Self: @@ -1382,6 +1388,7 @@ def apply_matrix( def apply_complex_function( self, function: Callable[[complex], complex], + *, about_point: Point3DLike | None = None, about_edge: Vector3D | None = None, ) -> Self: @@ -1416,7 +1423,7 @@ def R3_func(point): xy_complex = function(complex(x, y)) return [xy_complex.real, xy_complex.imag, z] - return self.apply_function(R3_func, about_point, about_edge) + return self.apply_function(R3_func, about_point=about_point, about_edge=about_edge) def reverse_points(self) -> Self: for mob in self.family_members_with_points(): diff --git a/manim/mobject/types/vectorized_mobject.py b/manim/mobject/types/vectorized_mobject.py index c967306c5c..72ba4732b9 100644 --- a/manim/mobject/types/vectorized_mobject.py +++ b/manim/mobject/types/vectorized_mobject.py @@ -472,7 +472,14 @@ def set_opacity(self, opacity: float, family: bool = True) -> Self: self.set_stroke(opacity=opacity, family=family, background=True) return self - def scale(self, scale_factor: float, scale_stroke: bool = False, **kwargs) -> Self: + def scale( + self, + scale_factor: float, + scale_stroke: bool = False, + *, + about_point: Point3DLike | None = None, + about_edge: Vector3D | None = None, + ) -> Self: r"""Scale the size by a factor. Default behavior is to scale about the center of the vmobject. @@ -527,7 +534,7 @@ def construct(self): width=abs(scale_factor) * self.get_stroke_width(background=True), background=True, ) - super().scale(scale_factor, **kwargs) + super().scale(scale_factor, about_point=about_point, about_edge=about_edge) return self def fade(self, darkness: float = 0.5, family: bool = True) -> Self: @@ -1178,7 +1185,13 @@ def append_vectorized_mobject(self, vectorized_mobject: VMobject) -> None: self.points = self.points[:-1] self.append_points(vectorized_mobject.points) - def apply_function(self, function: MappingFunction) -> Self: + def apply_function( + self, + function: MappingFunction, + *, + about_point: Point3DLike | None = None, + about_edge: Vector3D | None = None, + ) -> Self: factor = self.pre_function_handle_to_anchor_scale_factor self.scale_handle_to_anchor_distances(factor) super().apply_function(function) @@ -1192,10 +1205,11 @@ def rotate( angle: float, axis: Vector3D = OUT, about_point: Point3DLike | None = None, + *, about_edge: Vector3D | None = None, ) -> Self: self.rotate_sheen_direction(angle, axis) - super().rotate(angle, axis, about_point, about_edge) + super().rotate(angle, axis, about_point, about_edge=about_edge) return self def scale_handle_to_anchor_distances(self, factor: float) -> Self: From 0768c991bf6e7beb186b8a0aeac1fd956714ffd4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 19:55:04 +0000 Subject: [PATCH 5/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- manim/mobject/mobject.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/manim/mobject/mobject.py b/manim/mobject/mobject.py index c114dcd113..705e6a677d 100644 --- a/manim/mobject/mobject.py +++ b/manim/mobject/mobject.py @@ -1321,7 +1321,9 @@ def construct(self): self.add(s2) """ - return self.rotate(TAU / 2, axis, about_point=about_point, about_edge=about_edge) + return self.rotate( + TAU / 2, axis, about_point=about_point, about_edge=about_edge + ) def stretch( self, @@ -1423,7 +1425,9 @@ def R3_func(point): xy_complex = function(complex(x, y)) return [xy_complex.real, xy_complex.imag, z] - return self.apply_function(R3_func, about_point=about_point, about_edge=about_edge) + return self.apply_function( + R3_func, about_point=about_point, about_edge=about_edge + ) def reverse_points(self) -> Self: for mob in self.family_members_with_points():