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).
This commit is contained in:
viniciuspalmieri 2026-05-27 10:23:02 -03:00
commit 5b31592ebb
2 changed files with 65 additions and 13 deletions

51
manim/mobject/_fit.py Normal file
View file

@ -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)

View file

@ -22,6 +22,7 @@ from typing import TYPE_CHECKING, Any, cast
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 @@ class Mobject:
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 @@ class Mobject:
>>> 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 @@ class Mobject:
>>> 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 @@ class Mobject:
>>> 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 @@ class Mobject:
>>> 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