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: 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