From bda9dab3dca4a541df77673b67b7d1f8068ec007 Mon Sep 17 00:00:00 2001 From: viniciuspalmieri Date: Wed, 27 May 2026 10:22:25 -0300 Subject: [PATCH 1/2] Reduce # type: ignore count in _config/utils.py The file currently carries 10 # type: ignore markers. Six of them exist only because annotations are slightly looser than reality, not because the underlying code is genuinely hard to type. This commit tightens those annotations and removes the markers: - Two [operator] ignores at the frame_y_radius/frame_x_radius getters go away by changing _d's annotation from dict[str, Any | None] to dict[str, Any] (the | None was what made `_d["frame_height"] / 2` illegal; values are still allowed to be None at runtime via Any). - One [arg-type] ignore in __deepcopy__ goes away by correcting the memo parameter annotation: copy.deepcopy's memo uses int keys (object ids), not str. - Three [func-returns-value] ignores in the frame_y_radius, frame_x_radius and frame_size setters go away by rewriting the `a.__setitem__(...) or b.__setitem__(...)` chain hack as two plain assignment statements. Same behaviour, easier to read. The remaining four markers (Liskov in update(), has-type bootstrap on _tex_template, literal-required on a QUALITIES key, dynamic setattr on a lambda) are structurally harder and out of scope here. mypy manim/_config/utils.py is clean after the change and the existing tests/test_config.py suite still passes. --- manim/_config/utils.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/manim/_config/utils.py b/manim/_config/utils.py index 3e45846539..da93865b1f 100644 --- a/manim/_config/utils.py +++ b/manim/_config/utils.py @@ -327,7 +327,7 @@ class MyScene(Scene): ... } def __init__(self) -> None: - self._d: dict[str, Any | None] = dict.fromkeys(self._OPTS) + self._d: dict[str, Any] = dict.fromkeys(self._OPTS) # behave like a dict def __iter__(self) -> Iterator[str]: @@ -423,13 +423,13 @@ def __copy__(self) -> Self: """See ManimConfig.copy().""" return copy.deepcopy(self) - def __deepcopy__(self, memo: dict[str, Any]) -> Self: + def __deepcopy__(self, memo: dict[int, Any]) -> Self: """See ManimConfig.copy().""" c = type(self)() # Deepcopying the underlying dict is enough because all properties # either read directly from it or compute their value on the fly from # values read directly from it. - c._d = copy.deepcopy(self._d, memo) # type: ignore[arg-type] + c._d = copy.deepcopy(self._d, memo) return c # helper type-checking methods @@ -1142,24 +1142,22 @@ def frame_width(self, value: float) -> None: @property def frame_y_radius(self) -> float: """Half the frame height (no flag).""" - return self._d["frame_height"] / 2 # type: ignore[operator] + return self._d["frame_height"] / 2 @frame_y_radius.setter def frame_y_radius(self, value: float) -> None: - self._d.__setitem__("frame_y_radius", value) or self._d.__setitem__( # type: ignore[func-returns-value] - "frame_height", 2 * value - ) + self._d["frame_y_radius"] = value + self._d["frame_height"] = 2 * value @property def frame_x_radius(self) -> float: """Half the frame width (no flag).""" - return self._d["frame_width"] / 2 # type: ignore[operator] + return self._d["frame_width"] / 2 @frame_x_radius.setter def frame_x_radius(self, value: float) -> None: - self._d.__setitem__("frame_x_radius", value) or self._d.__setitem__( # type: ignore[func-returns-value] - "frame_width", 2 * value - ) + self._d["frame_x_radius"] = value + self._d["frame_width"] = 2 * value @property def top(self) -> Vector3D: @@ -1290,9 +1288,8 @@ def frame_size(self) -> tuple[int, int]: @frame_size.setter def frame_size(self, value: tuple[int, int]) -> None: - self._d.__setitem__("pixel_width", value[0]) or self._d.__setitem__( # type: ignore[func-returns-value] - "pixel_height", value[1] - ) + self._d["pixel_width"] = value[0] + self._d["pixel_height"] = value[1] @property def quality(self) -> str | None: From 5b31592ebbbea0c755c873dd32b98b9d10618a87 Mon Sep 17 00:00:00 2001 From: viniciuspalmieri Date: Wed, 27 May 2026 10:23:02 -0300 Subject: [PATCH 2/2] Extract fit-to-size implementations from mobject.py into _fit utility module mobject.py is by far the largest file in manim/. As a first scoped step toward reducing its size, this commit moves the implementation of the fit-to-size family out of the Mobject class body and into a new manim/mobject/_fit.py module of free functions. The seven affected methods (rescale_to_fit, scale_to_fit_width, stretch_to_fit_width, scale_to_fit_height, stretch_to_fit_height, scale_to_fit_depth, stretch_to_fit_depth) remain defined on Mobject as thin stubs that delegate to the corresponding functions in _fit. The public API is unchanged: `Square().scale_to_fit_width(5)` works identically. The shape (utility module + delegation, rather than a mixin) follows the suggestion in issue discussion and matches the established pattern in manim/utils/ (space_ops, iterables, etc.). It also avoids the # type: ignore markers a mixin would need, since the utility functions take the mobject as a regular parameter. The substantive logic that moves out is the body of rescale_to_fit (the six wrappers are one-liners). mobject.py's overall LOC count is roughly unchanged because the method declarations and docstrings stay, but the actual implementation now lives in _fit.py (51 LOC). This sets a precedent for further cohesive groups (transforms, positioning, color) without claiming a position on the broader split. Validation: ruff clean on the touched files, mypy clean on _fit.py, pytest passes on tests/module/mobject/mobject/ and tests/test_config.py (30 passed on the non-LaTeX-dependent subset; the three failures I see locally on Windows are environmental and fail identically against upstream main). --- manim/mobject/_fit.py | 51 ++++++++++++++++++++++++++++++++++++++++ manim/mobject/mobject.py | 27 +++++++++++---------- 2 files changed, 65 insertions(+), 13 deletions(-) create mode 100644 manim/mobject/_fit.py diff --git a/manim/mobject/_fit.py b/manim/mobject/_fit.py new file mode 100644 index 0000000000..1e77bed86e --- /dev/null +++ b/manim/mobject/_fit.py @@ -0,0 +1,51 @@ +"""Fit-to-size operations for :class:`~.Mobject`. + +Standalone functions that take a mobject as their first argument; the +corresponding methods on :class:`Mobject` are thin delegations to these +helpers. This keeps :class:`Mobject`'s public API unchanged while moving +the implementation out of the already-large ``mobject.py``. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from manim.mobject.mobject import Mobject + + +def rescale_to_fit( + mob: Mobject, length: float, dim: int, stretch: bool = False, **kwargs: Any +) -> Mobject: + old_length = mob.length_over_dim(dim) + if old_length == 0: + return mob + if stretch: + mob.stretch(length / old_length, dim, **kwargs) + else: + mob.scale(length / old_length, **kwargs) + return mob + + +def scale_to_fit_width(mob: Mobject, width: float, **kwargs: Any) -> Mobject: + return rescale_to_fit(mob, width, 0, stretch=False, **kwargs) + + +def stretch_to_fit_width(mob: Mobject, width: float, **kwargs: Any) -> Mobject: + return rescale_to_fit(mob, width, 0, stretch=True, **kwargs) + + +def scale_to_fit_height(mob: Mobject, height: float, **kwargs: Any) -> Mobject: + return rescale_to_fit(mob, height, 1, stretch=False, **kwargs) + + +def stretch_to_fit_height(mob: Mobject, height: float, **kwargs: Any) -> Mobject: + return rescale_to_fit(mob, height, 1, stretch=True, **kwargs) + + +def scale_to_fit_depth(mob: Mobject, depth: float, **kwargs: Any) -> Mobject: + return rescale_to_fit(mob, depth, 2, stretch=False, **kwargs) + + +def stretch_to_fit_depth(mob: Mobject, depth: float, **kwargs: Any) -> Mobject: + return rescale_to_fit(mob, depth, 2, stretch=True, **kwargs) diff --git a/manim/mobject/mobject.py b/manim/mobject/mobject.py index 721ce57356..c635f06e50 100644 --- a/manim/mobject/mobject.py +++ b/manim/mobject/mobject.py @@ -22,6 +22,7 @@ import numpy as np from manim.data_structures import MethodWithArgs +from manim.mobject import _fit from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL from .. import config, logger @@ -1752,13 +1753,7 @@ def stretch_about_point(self, factor: float, dim: int, point: Point3DLike) -> Se def rescale_to_fit( self, length: float, dim: int, stretch: bool = False, **kwargs: Any ) -> Self: - old_length = self.length_over_dim(dim) - if old_length == 0: - return self - if stretch: - self.stretch(length / old_length, dim, **kwargs) - else: - self.scale(length / old_length, **kwargs) + _fit.rescale_to_fit(self, length, dim, stretch=stretch, **kwargs) return self def scale_to_fit_width(self, width: float, **kwargs: Any) -> Self: @@ -1784,7 +1779,8 @@ def scale_to_fit_width(self, width: float, **kwargs: Any) -> Self: >>> sq.height np.float64(5.0) """ - return self.rescale_to_fit(width, 0, stretch=False, **kwargs) + _fit.scale_to_fit_width(self, width, **kwargs) + return self def stretch_to_fit_width(self, width: float, **kwargs: Any) -> Self: """Stretches the :class:`~.Mobject` to fit a width, not keeping height/depth proportional. @@ -1809,7 +1805,8 @@ def stretch_to_fit_width(self, width: float, **kwargs: Any) -> Self: >>> sq.height np.float64(2.0) """ - return self.rescale_to_fit(width, 0, stretch=True, **kwargs) + _fit.stretch_to_fit_width(self, width, **kwargs) + return self def scale_to_fit_height(self, height: float, **kwargs: Any) -> Self: """Scales the :class:`~.Mobject` to fit a height while keeping width/depth proportional. @@ -1834,7 +1831,8 @@ def scale_to_fit_height(self, height: float, **kwargs: Any) -> Self: >>> sq.width np.float64(5.0) """ - return self.rescale_to_fit(height, 1, stretch=False, **kwargs) + _fit.scale_to_fit_height(self, height, **kwargs) + return self def stretch_to_fit_height(self, height: float, **kwargs: Any) -> Self: """Stretches the :class:`~.Mobject` to fit a height, not keeping width/depth proportional. @@ -1859,15 +1857,18 @@ def stretch_to_fit_height(self, height: float, **kwargs: Any) -> Self: >>> sq.width np.float64(2.0) """ - return self.rescale_to_fit(height, 1, stretch=True, **kwargs) + _fit.stretch_to_fit_height(self, height, **kwargs) + return self def scale_to_fit_depth(self, depth: float, **kwargs: Any) -> Self: """Scales the :class:`~.Mobject` to fit a depth while keeping width/height proportional.""" - return self.rescale_to_fit(depth, 2, stretch=False, **kwargs) + _fit.scale_to_fit_depth(self, depth, **kwargs) + return self def stretch_to_fit_depth(self, depth: float, **kwargs: Any) -> Self: """Stretches the :class:`~.Mobject` to fit a depth, not keeping width/height proportional.""" - return self.rescale_to_fit(depth, 2, stretch=True, **kwargs) + _fit.stretch_to_fit_depth(self, depth, **kwargs) + return self def set_coord( self, value: float, dim: int, direction: Vector3DLike = ORIGIN