Skip to content

Fix type errors, enable type checking for mobject/geometry and add typings for the transformation functions #4228

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion manim/mobject/geometry/arc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions manim/mobject/geometry/boolean_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion manim/mobject/graphing/scale.py
Original file line number Diff line number Diff line change
Expand Up @@ -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``.

Expand Down
86 changes: 65 additions & 21 deletions manim/mobject/mobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
PathFuncType,
PixelArray,
Point3D,
Point3D_Array,
Point3DLike,
Point3DLike_Array,
Vector3D,
Expand Down Expand Up @@ -1225,7 +1226,13 @@

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:
Comment on lines +1229 to +1235

Check notice

Code scanning / CodeQL

Mismatch between signature and use of an overridden method Note

Overridden method signature does not match
call
, where it is passed too many arguments. Overriding method
method VMobject.scale
matches the call.
Overridden method signature does not match
call
, where it is passed too many arguments. Overriding method
method VMobject.scale
matches the call.
Overridden method signature does not match
call
, where it is passed too many arguments. Overriding method
method VMobject.scale
matches the call.
Overridden method signature does not match
call
, where it is passed too many arguments. Overriding method
method VMobject.scale
matches the call.
Overridden method signature does not match
call
, where it is passed too many arguments. Overriding method
method VMobject.scale
matches the call.
r"""Scale the size by a factor.

Default behavior is to scale about the center of the mobject.
Expand All @@ -1236,9 +1243,10 @@
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
-------
Expand Down Expand Up @@ -1267,7 +1275,7 @@

"""
self.apply_points_function_about_point(
lambda points: scale_factor * points, **kwargs
lambda points: scale_factor * points, about_point, about_edge
)
return self

Expand All @@ -1280,16 +1288,23 @@
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
Expand All @@ -1306,26 +1321,43 @@
self.add(s2)

"""
return self.rotate(TAU / 2, axis, **kwargs)
return self.rotate(
TAU / 2, axis, about_point=about_point, about_edge=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 is None and about_edge is 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:
Expand All @@ -1337,20 +1369,30 @@
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 is None and about_edge is 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.
Expand Down Expand Up @@ -1383,7 +1425,9 @@
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_point, about_edge=about_edge
)

def reverse_points(self) -> Self:
for mob in self.family_members_with_points():
Expand Down
24 changes: 19 additions & 5 deletions manim/mobject/types/vectorized_mobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand All @@ -1192,10 +1205,11 @@ 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=about_edge)
return self

def scale_handle_to_anchor_distances(self, factor: float) -> Self:
Expand Down
10 changes: 5 additions & 5 deletions manim/utils/bezier.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions manim/utils/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 4 additions & 2 deletions manim/utils/space_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -802,14 +802,16 @@ 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:
Expand Down
2 changes: 1 addition & 1 deletion mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down