mirror of
https://github.com/ManimCommunity/manim.git
synced 2026-06-22 10:01:47 +00:00
Add type annotations to scene.py and vector_space_scene.py (#4260)
* Stop ignoring errors from manim/scene.py Count: 307 errors Type annotations on scene/vector_space_scene.py Add type annotations to scene/vector_space_scene.py Mypy count: 210 Reverting two changes that triggers an error in the automatic testing. Further work on type hinting. Avoid forwarding positional arguments from Arrow to Line in the constructor. Revert "Avoid forwarding positional arguments from Arrow to Line in the constructor." This reverts commit80ae8576c1. Removed several type ignore statements and addressed comments from JasonGrace2282 Revert "Activate mypy check of mobject.geometry.*" This reverts commitd477c9a994. Revert "Removed several type ignore statements and addressed comments from JasonGrace2282" This reverts commit07bbe3f220. Added type annotations to zoomed_scene.py Error count: 308 -> 303 Adding type annotations to all methods in vector_space_scene.py Error count: 303 -> 272 Get rid of no-untyped-call errors from my in the vector_space_scene.py file Error count: 272 -> 343 Handle type issues related to ManimColor in vector_space_scene.py Handle var-annotated issues in vector_space_scene.py Error count: 332 -> 330 Handling has-type type errors in vector_space_scene.py Error count: 330 -> 285 Handled name-defined type issues in vector_space_scene.py Error count: 285 -> 282 Address type issue with calling an untyped method. Error count: 282 -> 281 Fix some typing issues in transform_mathcing_parts.py Change stroke_width to float in vector_space_scene.py Handled a few type errors. Error count: 267 Handled several typing issues in three_d_scene.py Error count: 267 -> 248 Dealing with type errors in scene_file_writer.py Error count: 248 -> 216 Ensured that all methods in scene.py have type declarations. Error count: 216 -> 225 Handle type issues related to interactivity by asserting that the camera is the OpenGLCamera Error count: 225 -> 182 Handle type issues in scene.py Error count: 182 -> 167 Asserting that the renderer or camera is of the proper type to use certain methods. This is mainly related to interactive elements and the 3D camera used in the ThreeDScene Error count: 167 -> 143 Avoid cyclic import of dependencies Error count: 143 -> 143 Handling no-untyped-call type errors in manim/scene/scene.py Error count: 143 -> 131 Handling assignment type errors in manim/scene/*.py Error count: 131 -> 121 Handling arg-type type errors in manim/scene/*.py Error count: 121 -> 116 Handling arg-type type errors in manim/scene/*.py Error count: 116 -> 112 Fixing various type errors Error count: 112 -> 102 Fixing various type errors Error count: 102 -> 97 Fixing various type errors Error count: 97 -> 90 Some aggressive changes to silence a significant number of type errors. Error count: 90 -> 66 Commented out an import (IPython) that makes the CI tests fail. Fix various type errors. More type annotations. Code cleanup. Remove the property mobject_updater_lists of the Scene class as it is not used anywhere. Handle import-untyped typing issues. More work on type annotations in manin/scene/.* More work on scenes/*.py looking at the dependency opengl_renderer.py More work on types in scenes/*.py Ignored an old bunch of type ignore statements. More work on dependencies for scene.py More work on dependencies for scene.py * Stop ignoring errors from manim/scene/scene.py mypy error count: 307 * Adding type annotations to scene.py (C1) * Adding type annotations to scene.py (C2.1) * Adding type annotations to scene.py (C2.2) * Adding type annotations to scene.py (C2.3) * Adding type annotations to scene.py (C3) * Adding type annotations to scene.py (C4) * Adding type annotations to scene.py (C5) * Adding type annotations to scene.py (C6) * Adding type annotations to scene.py (C7) * ... * Focus on scene.py * Adding type annotations to vector_space_scene.py * Added types to opengl_renderer.py * Added types to cairo_renderer.py * Fixed the last mypy errors in scene.py - many was ignored * Fixed the last mypy errors in vector_space_scene.py - many was ignored * Got rid of the last mypy errors. * Activate mypy check of vector_space_scene.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Code cleanup. * Update manim/gui/gui.py Co-authored-by: Francisco Manríquez Novoa <49853152+chopan050@users.noreply.github.com> * Update manim/renderer/opengl_renderer.py Co-authored-by: Francisco Manríquez Novoa <49853152+chopan050@users.noreply.github.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update manim/scene/scene.py Co-authored-by: Francisco Manríquez Novoa <49853152+chopan050@users.noreply.github.com> * Update manim/scene/scene.py Co-authored-by: Francisco Manríquez Novoa <49853152+chopan050@users.noreply.github.com> * Update manim/scene/scene.py Co-authored-by: Francisco Manríquez Novoa <49853152+chopan050@users.noreply.github.com> * Update manim/scene/scene.py Co-authored-by: Francisco Manríquez Novoa <49853152+chopan050@users.noreply.github.com> * Update manim/scene/scene.py Co-authored-by: Francisco Manríquez Novoa <49853152+chopan050@users.noreply.github.com> * Implementing suggestions from chopan50 * Explicitly mention all files where type errors are ignored in mypy.ini # Conflicts: # mypy.ini * Fix issue * Updates based on comments from chopan50 * Updates suggested by Chopan50 * Addressed more comments from chopan50 * Addressing more comments from chopan50 * Added docstring to SceneInteractAction * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Differentiate between _AnimationBuilder from an mobject and an opengl_mobject * Avoid a nameclash with the mobject module and variable name * Last touches. * Rolled back some changes related to _AnimationBuilder --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Francisco Manríquez Novoa <49853152+chopan050@users.noreply.github.com>
This commit is contained in:
parent
c4b7a80258
commit
ea16d22735
9 changed files with 453 additions and 270 deletions
|
|
@ -540,7 +540,7 @@ class Animation:
|
|||
|
||||
|
||||
def prepare_animation(
|
||||
anim: Animation | mobject._AnimationBuilder,
|
||||
anim: Animation | mobject._AnimationBuilder | opengl_mobject._AnimationBuilder,
|
||||
) -> Animation:
|
||||
r"""Returns either an unchanged animation, or the animation built
|
||||
from a passed animation factory.
|
||||
|
|
|
|||
|
|
@ -10,9 +10,16 @@ except ImportError:
|
|||
dearpygui_imported = False
|
||||
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from .. import __version__, config
|
||||
from ..utils.module_ops import scene_classes_from_file
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..renderer.opengl_renderer import OpenGLRenderer
|
||||
|
||||
|
||||
__all__ = ["configure_pygui"]
|
||||
|
||||
if dearpygui_imported:
|
||||
|
|
@ -20,7 +27,9 @@ if dearpygui_imported:
|
|||
window = dpg.generate_uuid()
|
||||
|
||||
|
||||
def configure_pygui(renderer, widgets, update=True):
|
||||
def configure_pygui(
|
||||
renderer: OpenGLRenderer, widgets: Sequence[dict[str, Any]], update: bool = True
|
||||
) -> None:
|
||||
if not dearpygui_imported:
|
||||
raise RuntimeError("Attempted to use DearPyGUI when it isn't imported.")
|
||||
if update:
|
||||
|
|
|
|||
|
|
@ -400,7 +400,7 @@ class Matrix(VMobject, metaclass=ConvertToOpenGL):
|
|||
mob.add_background_rectangle()
|
||||
return self
|
||||
|
||||
def get_mob_matrix(self):
|
||||
def get_mob_matrix(self) -> list[list[MathTex]]:
|
||||
"""Return the underlying mob matrix mobjects.
|
||||
|
||||
Returns
|
||||
|
|
@ -435,13 +435,13 @@ class Matrix(VMobject, metaclass=ConvertToOpenGL):
|
|||
"""
|
||||
return self.elements
|
||||
|
||||
def get_brackets(self):
|
||||
def get_brackets(self) -> VGroup:
|
||||
r"""Return the bracket mobjects.
|
||||
|
||||
Returns
|
||||
--------
|
||||
List[:class:`~.VGroup`]
|
||||
Each VGroup contains a bracket
|
||||
:class:`~.VGroup`
|
||||
A VGroup containing the left and right bracket.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import re
|
|||
from collections.abc import Iterable
|
||||
from functools import reduce
|
||||
from textwrap import dedent
|
||||
from typing import Any
|
||||
|
||||
from manim import config, logger
|
||||
from manim.constants import *
|
||||
|
|
@ -447,7 +448,11 @@ class Tex(MathTex):
|
|||
"""
|
||||
|
||||
def __init__(
|
||||
self, *tex_strings, arg_separator="", tex_environment="center", **kwargs
|
||||
self,
|
||||
*tex_strings: str,
|
||||
arg_separator: str = "",
|
||||
tex_environment: str = "center",
|
||||
**kwargs: Any,
|
||||
):
|
||||
super().__init__(
|
||||
*tex_strings,
|
||||
|
|
|
|||
|
|
@ -33,11 +33,11 @@ class CairoRenderer:
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
file_writer_class=SceneFileWriter,
|
||||
camera_class=None,
|
||||
skip_animations=False,
|
||||
**kwargs,
|
||||
):
|
||||
file_writer_class: type[SceneFileWriter] = SceneFileWriter,
|
||||
camera_class: type[Camera] | None = None,
|
||||
skip_animations: bool = False,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
# All of the following are set to EITHER the value passed via kwargs,
|
||||
# OR the value stored in the global config dict at the time of
|
||||
# _instance construction_.
|
||||
|
|
@ -51,7 +51,7 @@ class CairoRenderer:
|
|||
self.time = 0
|
||||
self.static_image = None
|
||||
|
||||
def init_scene(self, scene):
|
||||
def init_scene(self, scene: Scene) -> None:
|
||||
self.file_writer: Any = self._file_writer_class(
|
||||
self,
|
||||
scene.__class__.__name__,
|
||||
|
|
@ -119,12 +119,12 @@ class CairoRenderer:
|
|||
|
||||
def update_frame( # TODO Description in Docstring
|
||||
self,
|
||||
scene,
|
||||
scene: Scene,
|
||||
mobjects: typing.Iterable[Mobject] | None = None,
|
||||
include_submobjects: bool = True,
|
||||
ignore_skipping: bool = True,
|
||||
**kwargs,
|
||||
):
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Update the frame.
|
||||
|
||||
Parameters
|
||||
|
|
@ -263,7 +263,7 @@ class CairoRenderer:
|
|||
self.skip_animations = True
|
||||
raise EndSceneEarlyException()
|
||||
|
||||
def scene_finished(self, scene):
|
||||
def scene_finished(self, scene: Scene) -> None:
|
||||
# If no animations in scene, render an image instead
|
||||
if self.num_plays:
|
||||
self.file_writer.finish()
|
||||
|
|
|
|||
|
|
@ -4,14 +4,18 @@ import contextlib
|
|||
import itertools as it
|
||||
import time
|
||||
from functools import cached_property
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import moderngl
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
from manim import config, logger
|
||||
from manim.mobject.opengl.opengl_mobject import OpenGLMobject, OpenGLPoint
|
||||
from manim.mobject.opengl.opengl_mobject import (
|
||||
OpenGLMobject,
|
||||
OpenGLPoint,
|
||||
_AnimationBuilder,
|
||||
)
|
||||
from manim.mobject.opengl.opengl_vectorized_mobject import OpenGLVMobject
|
||||
from manim.utils.caching import handle_caching_play
|
||||
from manim.utils.color import color_to_rgba
|
||||
|
|
@ -35,6 +39,14 @@ from .vectorized_mobject_rendering import (
|
|||
render_opengl_vectorized_mobject_stroke,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
from manim.animation.animation import Animation
|
||||
from manim.mobject.mobject import Mobject
|
||||
from manim.scene.scene import Scene
|
||||
|
||||
|
||||
__all__ = ["OpenGLCamera", "OpenGLRenderer"]
|
||||
|
||||
|
||||
|
|
@ -102,7 +114,7 @@ class OpenGLCamera(OpenGLMobject):
|
|||
self.euler_angles = euler_angles
|
||||
self.refresh_rotation_matrix()
|
||||
|
||||
def get_position(self):
|
||||
def get_position(self) -> Point3D:
|
||||
return self.model_matrix[:, 3][:3]
|
||||
|
||||
def set_position(self, position):
|
||||
|
|
@ -123,7 +135,7 @@ class OpenGLCamera(OpenGLMobject):
|
|||
self.set_height(self.frame_shape[1], stretch=True)
|
||||
self.move_to(self.center_point)
|
||||
|
||||
def to_default_state(self):
|
||||
def to_default_state(self) -> Self:
|
||||
self.center()
|
||||
self.set_height(config["frame_height"])
|
||||
self.set_width(config["frame_width"])
|
||||
|
|
@ -166,28 +178,28 @@ class OpenGLCamera(OpenGLMobject):
|
|||
self.refresh_rotation_matrix()
|
||||
return self
|
||||
|
||||
def set_theta(self, theta):
|
||||
def set_theta(self, theta: float) -> Self:
|
||||
return self.set_euler_angles(theta=theta)
|
||||
|
||||
def set_phi(self, phi):
|
||||
def set_phi(self, phi: float) -> Self:
|
||||
return self.set_euler_angles(phi=phi)
|
||||
|
||||
def set_gamma(self, gamma):
|
||||
def set_gamma(self, gamma: float) -> Self:
|
||||
return self.set_euler_angles(gamma=gamma)
|
||||
|
||||
def increment_theta(self, dtheta):
|
||||
def increment_theta(self, dtheta: float) -> Self:
|
||||
self.euler_angles[0] += dtheta
|
||||
self.refresh_rotation_matrix()
|
||||
return self
|
||||
|
||||
def increment_phi(self, dphi):
|
||||
def increment_phi(self, dphi: float) -> Self:
|
||||
phi = self.euler_angles[1]
|
||||
new_phi = clip(phi + dphi, -PI / 2, PI / 2)
|
||||
self.euler_angles[1] = new_phi
|
||||
self.refresh_rotation_matrix()
|
||||
return self
|
||||
|
||||
def increment_gamma(self, dgamma):
|
||||
def increment_gamma(self, dgamma: float) -> Self:
|
||||
self.euler_angles[2] += dgamma
|
||||
self.refresh_rotation_matrix()
|
||||
return self
|
||||
|
|
@ -199,15 +211,15 @@ class OpenGLCamera(OpenGLMobject):
|
|||
# Assumes first point is at the center
|
||||
return self.points[0]
|
||||
|
||||
def get_width(self):
|
||||
def get_width(self) -> float:
|
||||
points = self.points
|
||||
return points[2, 0] - points[1, 0]
|
||||
|
||||
def get_height(self):
|
||||
def get_height(self) -> float:
|
||||
points = self.points
|
||||
return points[4, 1] - points[3, 1]
|
||||
|
||||
def get_focal_distance(self):
|
||||
def get_focal_distance(self) -> float:
|
||||
return self.focal_distance * self.get_height()
|
||||
|
||||
def interpolate(self, *args, **kwargs):
|
||||
|
|
@ -412,7 +424,12 @@ class OpenGLRenderer:
|
|||
raise EndSceneEarlyException()
|
||||
|
||||
@handle_caching_play
|
||||
def play(self, scene, *args, **kwargs):
|
||||
def play(
|
||||
self,
|
||||
scene: Scene,
|
||||
*args: Animation | Mobject | _AnimationBuilder,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
# TODO: Handle data locking / unlocking.
|
||||
self.animation_start_time = time.time()
|
||||
self.file_writer.begin_animation(not self.skip_animations)
|
||||
|
|
@ -440,11 +457,13 @@ class OpenGLRenderer:
|
|||
self.time += scene.duration
|
||||
self.num_plays += 1
|
||||
|
||||
def clear_screen(self):
|
||||
def clear_screen(self) -> None:
|
||||
self.frame_buffer_object.clear(*self.background_color)
|
||||
self.window.swap_buffers()
|
||||
|
||||
def render(self, scene, frame_offset, moving_mobjects):
|
||||
def render(
|
||||
self, scene: Scene, frame_offset, moving_mobjects: list[Mobject]
|
||||
) -> None:
|
||||
self.update_frame(scene)
|
||||
|
||||
if self.skip_animations:
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ from __future__ import annotations
|
|||
|
||||
from manim.utils.parameter_parsing import flatten_iterable_parameters
|
||||
|
||||
from ..mobject.mobject import _AnimationBuilder
|
||||
|
||||
__all__ = ["Scene"]
|
||||
|
||||
import copy
|
||||
|
|
@ -29,7 +31,7 @@ from typing import TYPE_CHECKING
|
|||
|
||||
import numpy as np
|
||||
from tqdm import tqdm
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
from watchdog.events import DirModifiedEvent, FileModifiedEvent, FileSystemEventHandler
|
||||
from watchdog.observers import Observer
|
||||
|
||||
from manim.mobject.mobject import Mobject
|
||||
|
|
@ -41,7 +43,7 @@ from ..camera.camera import Camera
|
|||
from ..constants import *
|
||||
from ..gui.gui import configure_pygui
|
||||
from ..renderer.cairo_renderer import CairoRenderer
|
||||
from ..renderer.opengl_renderer import OpenGLRenderer
|
||||
from ..renderer.opengl_renderer import OpenGLCamera, OpenGLMobject, OpenGLRenderer
|
||||
from ..renderer.shader import Object3D
|
||||
from ..utils import opengl, space_ops
|
||||
from ..utils.exceptions import EndSceneEarlyException, RerunSceneException
|
||||
|
|
@ -51,20 +53,39 @@ from ..utils.file_ops import open_media_file
|
|||
from ..utils.iterables import list_difference_update, list_update
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
from typing import Callable
|
||||
from collections.abc import Iterable, Sequence
|
||||
from types import FrameType
|
||||
from typing import Any, Callable, TypeAlias
|
||||
|
||||
from manim.mobject.mobject import _AnimationBuilder
|
||||
from typing_extensions import Self
|
||||
|
||||
from manim.typing import Point3D
|
||||
|
||||
SceneInteractAction: TypeAlias = tuple[str, Iterable[Any], dict[str, Any]]
|
||||
"""
|
||||
The SceneInteractAction type alias is used for elements in the queue
|
||||
used by Scene.interact().
|
||||
The elements consist consist of:
|
||||
|
||||
- a string, which is either the name of a Scene method or some special keyword
|
||||
starting with "rerun" or "exit",
|
||||
- a list of args for the Scene method (only used if the first string actually
|
||||
corresponds to a method) and
|
||||
- a dict of kwargs for the Scene method (if the first string corresponds to one.
|
||||
Otherwise, currently Scene.interact() extracts a possible "from_animation_number" from it if the first string starts with "rerun"),
|
||||
as seen around the source code where it's common to use self.queue.put((method_name, [], {})) and similar items.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class RerunSceneHandler(FileSystemEventHandler):
|
||||
"""A class to handle rerunning a Scene after the input file is modified."""
|
||||
|
||||
def __init__(self, queue):
|
||||
def __init__(self, queue: Queue[SceneInteractAction]) -> None:
|
||||
super().__init__()
|
||||
self.queue = queue
|
||||
|
||||
def on_modified(self, event):
|
||||
def on_modified(self, event: DirModifiedEvent | FileModifiedEvent) -> None:
|
||||
self.queue.put(("rerun_file", [], {}))
|
||||
|
||||
|
||||
|
|
@ -112,22 +133,22 @@ class Scene:
|
|||
self.random_seed = random_seed
|
||||
self.skip_animations = skip_animations
|
||||
|
||||
self.animations = None
|
||||
self.stop_condition = None
|
||||
self.moving_mobjects = []
|
||||
self.static_mobjects = []
|
||||
self.time_progression = None
|
||||
self.duration = None
|
||||
self.last_t = None
|
||||
self.queue = Queue()
|
||||
self.animations: list[Animation] | None = None
|
||||
self.stop_condition: Callable[[], bool] | None = None
|
||||
self.moving_mobjects: list[Mobject] = []
|
||||
self.static_mobjects: list[Mobject] = []
|
||||
self.time_progression: tqdm[float] | None = None
|
||||
self.duration: float | None = None
|
||||
self.last_t = 0.0
|
||||
self.queue: Queue[SceneInteractAction] = Queue()
|
||||
self.skip_animation_preview = False
|
||||
self.meshes = []
|
||||
self.meshes: list[Object3D] = []
|
||||
self.camera_target = ORIGIN
|
||||
self.widgets = []
|
||||
self.widgets: list[Any] = []
|
||||
self.dearpygui_imported = dearpygui_imported
|
||||
self.updaters = []
|
||||
self.key_to_function_map = {}
|
||||
self.mouse_press_callbacks = []
|
||||
self.updaters: list[Callable[[float], None]] = []
|
||||
self.key_to_function_map: dict[str, Callable[[], None]] = {}
|
||||
self.mouse_press_callbacks: list[Callable[[], None]] = []
|
||||
self.interactive_mode = False
|
||||
|
||||
if config.renderer == RendererType.OPENGL:
|
||||
|
|
@ -138,7 +159,9 @@ class Scene:
|
|||
renderer = OpenGLRenderer()
|
||||
|
||||
if renderer is None:
|
||||
self.renderer = CairoRenderer(
|
||||
self.renderer: CairoRenderer | OpenGLRenderer = CairoRenderer(
|
||||
# TODO: Is it a suitable approach to make an instance of
|
||||
# the self.camera_class here?
|
||||
camera_class=self.camera_class,
|
||||
skip_animations=self.skip_animations,
|
||||
)
|
||||
|
|
@ -146,15 +169,15 @@ class Scene:
|
|||
self.renderer = renderer
|
||||
self.renderer.init_scene(self)
|
||||
|
||||
self.mobjects = []
|
||||
self.mobjects: list[Mobject] = []
|
||||
# TODO, remove need for foreground mobjects
|
||||
self.foreground_mobjects = []
|
||||
self.foreground_mobjects: list[Mobject] = []
|
||||
if self.random_seed is not None:
|
||||
random.seed(self.random_seed)
|
||||
np.random.seed(self.random_seed)
|
||||
|
||||
@property
|
||||
def camera(self):
|
||||
def camera(self) -> Camera | OpenGLCamera:
|
||||
return self.renderer.camera
|
||||
|
||||
@property
|
||||
|
|
@ -162,7 +185,7 @@ class Scene:
|
|||
"""The time since the start of the scene."""
|
||||
return self.renderer.time
|
||||
|
||||
def __deepcopy__(self, clone_from_id):
|
||||
def __deepcopy__(self, clone_from_id: dict[int, Any]) -> Scene:
|
||||
cls = self.__class__
|
||||
result = cls.__new__(cls)
|
||||
clone_from_id[id(self)] = result
|
||||
|
|
@ -175,7 +198,7 @@ class Scene:
|
|||
|
||||
return result
|
||||
|
||||
def render(self, preview: bool = False):
|
||||
def render(self, preview: bool = False) -> bool:
|
||||
"""
|
||||
Renders this Scene.
|
||||
|
||||
|
|
@ -191,7 +214,8 @@ class Scene:
|
|||
pass
|
||||
except RerunSceneException:
|
||||
self.remove(*self.mobjects)
|
||||
self.renderer.clear_screen()
|
||||
# TODO: The CairoRenderer does not have the method clear_screen()
|
||||
self.renderer.clear_screen() # type: ignore[union-attr]
|
||||
self.renderer.num_plays = 0
|
||||
return True
|
||||
self.tear_down()
|
||||
|
|
@ -215,7 +239,9 @@ class Scene:
|
|||
if config["preview"] or config["show_in_file_browser"]:
|
||||
open_media_file(self.renderer.file_writer)
|
||||
|
||||
def setup(self):
|
||||
return False
|
||||
|
||||
def setup(self) -> None:
|
||||
"""
|
||||
This is meant to be implemented by any scenes which
|
||||
are commonly subclassed, and have some common setup
|
||||
|
|
@ -223,7 +249,7 @@ class Scene:
|
|||
"""
|
||||
pass
|
||||
|
||||
def tear_down(self):
|
||||
def tear_down(self) -> None:
|
||||
"""
|
||||
This is meant to be implemented by any scenes which
|
||||
are commonly subclassed, and have some common method
|
||||
|
|
@ -231,7 +257,7 @@ class Scene:
|
|||
"""
|
||||
pass
|
||||
|
||||
def construct(self):
|
||||
def construct(self) -> None:
|
||||
"""Add content to the Scene.
|
||||
|
||||
From within :meth:`Scene.construct`, display mobjects on screen by calling
|
||||
|
|
@ -276,10 +302,10 @@ class Scene:
|
|||
"""
|
||||
self.renderer.file_writer.next_section(name, section_type, skip_animations)
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return self.__class__.__name__
|
||||
|
||||
def get_attrs(self, *keys: str):
|
||||
def get_attrs(self, *keys: str) -> list[Any]:
|
||||
"""
|
||||
Gets attributes of a scene given the attribute's identifier/name.
|
||||
|
||||
|
|
@ -295,7 +321,7 @@ class Scene:
|
|||
"""
|
||||
return [getattr(self, key) for key in keys]
|
||||
|
||||
def update_mobjects(self, dt: float):
|
||||
def update_mobjects(self, dt: float) -> None:
|
||||
"""
|
||||
Begins updating all mobjects in the Scene.
|
||||
|
||||
|
|
@ -304,15 +330,15 @@ class Scene:
|
|||
dt
|
||||
Change in time between updates. Defaults (mostly) to 1/frames_per_second
|
||||
"""
|
||||
for mobject in self.mobjects:
|
||||
mobject.update(dt)
|
||||
for mobj in self.mobjects:
|
||||
mobj.update(dt)
|
||||
|
||||
def update_meshes(self, dt):
|
||||
def update_meshes(self, dt: float) -> None:
|
||||
for obj in self.meshes:
|
||||
for mesh in obj.get_family():
|
||||
for mesh in obj.get_family(): # type: ignore[no-untyped-call]
|
||||
mesh.update(dt)
|
||||
|
||||
def update_self(self, dt: float):
|
||||
def update_self(self, dt: float) -> None:
|
||||
"""Run all scene updater functions.
|
||||
|
||||
Among all types of update functions (mobject updaters, mesh updaters,
|
||||
|
|
@ -344,7 +370,9 @@ class Scene:
|
|||
|
||||
This is only called when a single Wait animation is played.
|
||||
"""
|
||||
assert self.animations is not None
|
||||
wait_animation = self.animations[0]
|
||||
assert isinstance(wait_animation, Wait)
|
||||
if wait_animation.is_static_wait is None:
|
||||
should_update = (
|
||||
self.always_update_mobjects
|
||||
|
|
@ -358,7 +386,7 @@ class Scene:
|
|||
wait_animation.is_static_wait = not should_update
|
||||
return not wait_animation.is_static_wait
|
||||
|
||||
def get_top_level_mobjects(self):
|
||||
def get_top_level_mobjects(self) -> list[Mobject]:
|
||||
"""
|
||||
Returns all mobjects which are not submobjects.
|
||||
|
||||
|
|
@ -371,13 +399,13 @@ class Scene:
|
|||
# of another mobject from the scene
|
||||
families = [m.get_family() for m in self.mobjects]
|
||||
|
||||
def is_top_level(mobject):
|
||||
def is_top_level(mobject: Mobject) -> bool:
|
||||
num_families = sum((mobject in family) for family in families)
|
||||
return num_families == 1
|
||||
|
||||
return list(filter(is_top_level, self.mobjects))
|
||||
|
||||
def get_mobject_family_members(self):
|
||||
def get_mobject_family_members(self) -> list[Mobject]:
|
||||
"""
|
||||
Returns list of family-members of all mobjects in scene.
|
||||
If a Circle() and a VGroup(Rectangle(),Triangle()) were added,
|
||||
|
|
@ -394,13 +422,14 @@ class Scene:
|
|||
for mob in self.mobjects:
|
||||
family_members.extend(mob.get_family())
|
||||
return family_members
|
||||
elif config.renderer == RendererType.CAIRO:
|
||||
else:
|
||||
assert config.renderer == RendererType.CAIRO
|
||||
return extract_mobject_family_members(
|
||||
self.mobjects,
|
||||
use_z_index=self.renderer.camera.use_z_index,
|
||||
)
|
||||
|
||||
def add(self, *mobjects: Mobject):
|
||||
def add(self, *mobjects: Mobject | OpenGLMobject) -> Self:
|
||||
"""
|
||||
Mobjects will be displayed, from background to
|
||||
foreground in the order with which they are added.
|
||||
|
|
@ -418,26 +447,30 @@ class Scene:
|
|||
"""
|
||||
if config.renderer == RendererType.OPENGL:
|
||||
new_mobjects = []
|
||||
new_meshes = []
|
||||
new_meshes: list[Object3D] = []
|
||||
for mobject_or_mesh in mobjects:
|
||||
if isinstance(mobject_or_mesh, Object3D):
|
||||
new_meshes.append(mobject_or_mesh)
|
||||
else:
|
||||
new_mobjects.append(mobject_or_mesh)
|
||||
self.remove(*new_mobjects)
|
||||
self.mobjects += new_mobjects
|
||||
self.remove(*new_meshes)
|
||||
self.remove(*new_mobjects) # type: ignore[arg-type]
|
||||
self.mobjects += new_mobjects # type: ignore[arg-type]
|
||||
self.remove(*new_meshes) # type: ignore[arg-type]
|
||||
self.meshes += new_meshes
|
||||
elif config.renderer == RendererType.CAIRO:
|
||||
mobjects = [*mobjects, *self.foreground_mobjects]
|
||||
self.restructure_mobjects(to_remove=mobjects)
|
||||
self.mobjects += mobjects
|
||||
else:
|
||||
assert config.renderer == RendererType.CAIRO
|
||||
new_and_foreground_mobjects: list[Mobject] = [
|
||||
*mobjects, # type: ignore[list-item]
|
||||
*self.foreground_mobjects,
|
||||
]
|
||||
self.restructure_mobjects(to_remove=new_and_foreground_mobjects)
|
||||
self.mobjects += new_and_foreground_mobjects
|
||||
if self.moving_mobjects:
|
||||
self.restructure_mobjects(
|
||||
to_remove=mobjects,
|
||||
to_remove=new_and_foreground_mobjects,
|
||||
mobject_list_name="moving_mobjects",
|
||||
)
|
||||
self.moving_mobjects += mobjects
|
||||
self.moving_mobjects += new_and_foreground_mobjects
|
||||
return self
|
||||
|
||||
def add_mobjects_from_animations(self, animations: list[Animation]) -> None:
|
||||
|
|
@ -450,9 +483,9 @@ class Scene:
|
|||
mob = animation.mobject
|
||||
if mob is not None and mob not in curr_mobjects:
|
||||
self.add(mob)
|
||||
curr_mobjects += mob.get_family()
|
||||
curr_mobjects += mob.get_family() # type: ignore[arg-type]
|
||||
|
||||
def remove(self, *mobjects: Mobject):
|
||||
def remove(self, *mobjects: Mobject) -> Self:
|
||||
"""
|
||||
Removes mobjects in the passed list of mobjects
|
||||
from the scene and the foreground, by removing them
|
||||
|
|
@ -465,7 +498,8 @@ class Scene:
|
|||
"""
|
||||
if config.renderer == RendererType.OPENGL:
|
||||
mobjects_to_remove = []
|
||||
meshes_to_remove = set()
|
||||
meshes_to_remove: set[Object3D] = set()
|
||||
mobject_or_mesh: Mobject
|
||||
for mobject_or_mesh in mobjects:
|
||||
if isinstance(mobject_or_mesh, Object3D):
|
||||
meshes_to_remove.add(mobject_or_mesh)
|
||||
|
|
@ -475,11 +509,16 @@ class Scene:
|
|||
self.mobjects,
|
||||
mobjects_to_remove,
|
||||
)
|
||||
|
||||
def lambda_function(mesh: Object3D) -> bool:
|
||||
return mesh not in set(meshes_to_remove)
|
||||
|
||||
self.meshes = list(
|
||||
filter(lambda mesh: mesh not in set(meshes_to_remove), self.meshes),
|
||||
filter(lambda_function, self.meshes),
|
||||
)
|
||||
return self
|
||||
elif config.renderer == RendererType.CAIRO:
|
||||
else:
|
||||
assert config.renderer == RendererType.CAIRO
|
||||
for list_name in "mobjects", "foreground_mobjects":
|
||||
self.restructure_mobjects(mobjects, list_name, False)
|
||||
return self
|
||||
|
|
@ -582,7 +621,7 @@ class Scene:
|
|||
to_remove: Sequence[Mobject],
|
||||
mobject_list_name: str = "mobjects",
|
||||
extract_families: bool = True,
|
||||
):
|
||||
) -> Scene:
|
||||
"""
|
||||
tl:wr
|
||||
If your scene has a Group(), and you removed a mobject from the Group,
|
||||
|
|
@ -620,7 +659,9 @@ class Scene:
|
|||
setattr(self, mobject_list_name, new_list)
|
||||
return self
|
||||
|
||||
def get_restructured_mobject_list(self, mobjects: list, to_remove: list):
|
||||
def get_restructured_mobject_list(
|
||||
self, mobjects: Iterable[Mobject], to_remove: Iterable[Mobject]
|
||||
) -> list[Mobject]:
|
||||
"""
|
||||
Given a list of mobjects and a list of mobjects to be removed, this
|
||||
filters out the removable mobjects from the list of mobjects.
|
||||
|
|
@ -639,9 +680,11 @@ class Scene:
|
|||
list
|
||||
The list of mobjects with the mobjects to remove removed.
|
||||
"""
|
||||
new_mobjects = []
|
||||
new_mobjects: list[Mobject] = []
|
||||
|
||||
def add_safe_mobjects_from_list(list_to_examine, set_to_remove):
|
||||
def add_safe_mobjects_from_list(
|
||||
list_to_examine: Iterable[Mobject], set_to_remove: set[Mobject]
|
||||
) -> None:
|
||||
for mob in list_to_examine:
|
||||
if mob in set_to_remove:
|
||||
continue
|
||||
|
|
@ -655,7 +698,7 @@ class Scene:
|
|||
return new_mobjects
|
||||
|
||||
# TODO, remove this, and calls to this
|
||||
def add_foreground_mobjects(self, *mobjects: Mobject):
|
||||
def add_foreground_mobjects(self, *mobjects: Mobject) -> Scene:
|
||||
"""
|
||||
Adds mobjects to the foreground, and internally to the list
|
||||
foreground_mobjects, and mobjects.
|
||||
|
|
@ -674,7 +717,7 @@ class Scene:
|
|||
self.add(*mobjects)
|
||||
return self
|
||||
|
||||
def add_foreground_mobject(self, mobject: Mobject):
|
||||
def add_foreground_mobject(self, mobject: Mobject) -> Scene:
|
||||
"""
|
||||
Adds a single mobject to the foreground, and internally to the list
|
||||
foreground_mobjects, and mobjects.
|
||||
|
|
@ -691,7 +734,7 @@ class Scene:
|
|||
"""
|
||||
return self.add_foreground_mobjects(mobject)
|
||||
|
||||
def remove_foreground_mobjects(self, *to_remove: Mobject):
|
||||
def remove_foreground_mobjects(self, *to_remove: Mobject) -> Scene:
|
||||
"""
|
||||
Removes mobjects from the foreground, and internally from the list
|
||||
foreground_mobjects.
|
||||
|
|
@ -709,7 +752,7 @@ class Scene:
|
|||
self.restructure_mobjects(to_remove, "foreground_mobjects")
|
||||
return self
|
||||
|
||||
def remove_foreground_mobject(self, mobject: Mobject):
|
||||
def remove_foreground_mobject(self, mobject: Mobject) -> Scene:
|
||||
"""
|
||||
Removes a single mobject from the foreground, and internally from the list
|
||||
foreground_mobjects.
|
||||
|
|
@ -726,7 +769,7 @@ class Scene:
|
|||
"""
|
||||
return self.remove_foreground_mobjects(mobject)
|
||||
|
||||
def bring_to_front(self, *mobjects: Mobject):
|
||||
def bring_to_front(self, *mobjects: Mobject) -> Scene:
|
||||
"""
|
||||
Adds the passed mobjects to the scene again,
|
||||
pushing them to he front of the scene.
|
||||
|
|
@ -745,7 +788,7 @@ class Scene:
|
|||
self.add(*mobjects)
|
||||
return self
|
||||
|
||||
def bring_to_back(self, *mobjects: Mobject):
|
||||
def bring_to_back(self, *mobjects: Mobject) -> Scene:
|
||||
"""
|
||||
Removes the mobject from the scene and
|
||||
adds them to the back of the scene.
|
||||
|
|
@ -765,7 +808,7 @@ class Scene:
|
|||
self.mobjects = list(mobjects) + self.mobjects
|
||||
return self
|
||||
|
||||
def clear(self):
|
||||
def clear(self) -> Self:
|
||||
"""
|
||||
Removes all mobjects present in self.mobjects
|
||||
and self.foreground_mobjects from the scene.
|
||||
|
|
@ -812,7 +855,9 @@ class Scene:
|
|||
return mobjects[i:]
|
||||
return []
|
||||
|
||||
def get_moving_and_static_mobjects(self, animations):
|
||||
def get_moving_and_static_mobjects(
|
||||
self, animations: Iterable[Animation]
|
||||
) -> tuple[list[Mobject], list[Mobject]]:
|
||||
all_mobjects = list_update(self.mobjects, self.foreground_mobjects)
|
||||
all_mobject_families = extract_mobject_family_members(
|
||||
all_mobjects,
|
||||
|
|
@ -833,8 +878,8 @@ class Scene:
|
|||
def compile_animations(
|
||||
self,
|
||||
*args: Animation | Mobject | _AnimationBuilder,
|
||||
**kwargs,
|
||||
):
|
||||
**kwargs: Any,
|
||||
) -> list[Animation]:
|
||||
"""
|
||||
Creates _MethodAnimations from any _AnimationBuilders and updates animation
|
||||
kwargs with kwargs passed to play().
|
||||
|
|
@ -856,7 +901,7 @@ class Scene:
|
|||
# Allow passing a generator to self.play instead of comma separated arguments
|
||||
for arg in arg_anims:
|
||||
try:
|
||||
animations.append(prepare_animation(arg))
|
||||
animations.append(prepare_animation(arg)) # type: ignore[arg-type]
|
||||
except TypeError as e:
|
||||
if inspect.ismethod(arg):
|
||||
raise TypeError(
|
||||
|
|
@ -876,7 +921,7 @@ class Scene:
|
|||
|
||||
def _get_animation_time_progression(
|
||||
self, animations: list[Animation], duration: float
|
||||
):
|
||||
) -> tqdm[float]:
|
||||
"""
|
||||
You will hardly use this when making your own animations.
|
||||
This method is for Manim's internal use.
|
||||
|
|
@ -929,10 +974,10 @@ class Scene:
|
|||
def get_time_progression(
|
||||
self,
|
||||
run_time: float,
|
||||
description,
|
||||
description: str,
|
||||
n_iterations: int | None = None,
|
||||
override_skip_animations: bool = False,
|
||||
):
|
||||
) -> tqdm[float]:
|
||||
"""
|
||||
You will hardly use this when making your own animations.
|
||||
This method is for Manim's internal use.
|
||||
|
|
@ -960,7 +1005,7 @@ class Scene:
|
|||
The CommandLine Progress Bar.
|
||||
"""
|
||||
if self.renderer.skip_animations and not override_skip_animations:
|
||||
times = [run_time]
|
||||
times: Iterable[float] = [run_time]
|
||||
else:
|
||||
step = 1 / config["frame_rate"]
|
||||
times = np.arange(0, run_time, step)
|
||||
|
|
@ -978,7 +1023,7 @@ class Scene:
|
|||
def validate_run_time(
|
||||
cls,
|
||||
run_time: float,
|
||||
method: Callable[[Any, ...], Any],
|
||||
method: Callable[[Any], Any],
|
||||
parameter_name: str = "run_time",
|
||||
) -> float:
|
||||
method_name = f"{cls.__name__}.{method.__name__}()"
|
||||
|
|
@ -1003,7 +1048,7 @@ class Scene:
|
|||
|
||||
return run_time
|
||||
|
||||
def get_run_time(self, animations: list[Animation]):
|
||||
def get_run_time(self, animations: list[Animation]) -> float:
|
||||
"""
|
||||
Gets the total run time for a list of animations.
|
||||
|
||||
|
|
@ -1025,11 +1070,11 @@ class Scene:
|
|||
def play(
|
||||
self,
|
||||
*args: Animation | Mobject | _AnimationBuilder,
|
||||
subcaption=None,
|
||||
subcaption_duration=None,
|
||||
subcaption_offset=0,
|
||||
**kwargs,
|
||||
):
|
||||
subcaption: str | None = None,
|
||||
subcaption_duration: float | None = None,
|
||||
subcaption_offset: float = 0,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
r"""Plays an animation in this scene.
|
||||
|
||||
Parameters
|
||||
|
|
@ -1094,7 +1139,7 @@ class Scene:
|
|||
duration: float = DEFAULT_WAIT_TIME,
|
||||
stop_condition: Callable[[], bool] | None = None,
|
||||
frozen_frame: bool | None = None,
|
||||
):
|
||||
) -> None:
|
||||
"""Plays a "no operation" animation.
|
||||
|
||||
Parameters
|
||||
|
|
@ -1125,7 +1170,7 @@ class Scene:
|
|||
)
|
||||
)
|
||||
|
||||
def pause(self, duration: float = DEFAULT_WAIT_TIME):
|
||||
def pause(self, duration: float = DEFAULT_WAIT_TIME) -> None:
|
||||
"""Pauses the scene (i.e., displays a frozen frame).
|
||||
|
||||
This is an alias for :meth:`.wait` with ``frozen_frame``
|
||||
|
|
@ -1143,7 +1188,9 @@ class Scene:
|
|||
duration = self.validate_run_time(duration, self.pause, "duration")
|
||||
self.wait(duration=duration, frozen_frame=True)
|
||||
|
||||
def wait_until(self, stop_condition: Callable[[], bool], max_time: float = 60):
|
||||
def wait_until(
|
||||
self, stop_condition: Callable[[], bool], max_time: float = 60
|
||||
) -> None:
|
||||
"""Wait until a condition is satisfied, up to a given maximum duration.
|
||||
|
||||
Parameters
|
||||
|
|
@ -1160,8 +1207,8 @@ class Scene:
|
|||
def compile_animation_data(
|
||||
self,
|
||||
*animations: Animation | Mobject | _AnimationBuilder,
|
||||
**play_kwargs,
|
||||
):
|
||||
**play_kwargs: Any,
|
||||
) -> Self | None:
|
||||
"""Given a list of animations, compile the corresponding
|
||||
static and moving mobjects, and gather the animation durations.
|
||||
|
||||
|
|
@ -1207,6 +1254,7 @@ class Scene:
|
|||
|
||||
def begin_animations(self) -> None:
|
||||
"""Start the animations of the scene."""
|
||||
assert self.animations is not None
|
||||
for animation in self.animations:
|
||||
animation._setup_scene(self)
|
||||
animation.begin()
|
||||
|
|
@ -1221,13 +1269,14 @@ class Scene:
|
|||
|
||||
def is_current_animation_frozen_frame(self) -> bool:
|
||||
"""Returns whether the current animation produces a static frame (generally a Wait)."""
|
||||
assert self.animations is not None
|
||||
return (
|
||||
isinstance(self.animations[0], Wait)
|
||||
and len(self.animations) == 1
|
||||
and self.animations[0].is_static_wait
|
||||
)
|
||||
|
||||
def play_internal(self, skip_rendering: bool = False):
|
||||
def play_internal(self, skip_rendering: bool = False) -> None:
|
||||
"""
|
||||
This method is used to prep the animations for rendering,
|
||||
apply the arguments and parameters required to them,
|
||||
|
|
@ -1238,6 +1287,7 @@ class Scene:
|
|||
skip_rendering
|
||||
Whether the rendering should be skipped, by default False
|
||||
"""
|
||||
assert self.animations is not None
|
||||
self.duration = self.get_run_time(self.animations)
|
||||
self.time_progression = self._get_animation_time_progression(
|
||||
self.animations,
|
||||
|
|
@ -1256,11 +1306,13 @@ class Scene:
|
|||
animation.clean_up_from_scene(self)
|
||||
if not self.renderer.skip_animations:
|
||||
self.update_mobjects(0)
|
||||
self.renderer.static_image = None
|
||||
# TODO: The OpenGLRenderer does not have the property static.image.
|
||||
self.renderer.static_image = None # type: ignore[union-attr]
|
||||
# Closing the progress bar at the end of the play.
|
||||
self.time_progression.close()
|
||||
|
||||
def check_interactive_embed_is_valid(self):
|
||||
def check_interactive_embed_is_valid(self) -> bool:
|
||||
assert isinstance(self.renderer, OpenGLRenderer)
|
||||
if config["force_window"]:
|
||||
return True
|
||||
if self.skip_animation_preview:
|
||||
|
|
@ -1285,23 +1337,28 @@ class Scene:
|
|||
return False
|
||||
return True
|
||||
|
||||
def interactive_embed(self):
|
||||
def interactive_embed(self) -> None:
|
||||
"""Like embed(), but allows for screen interaction."""
|
||||
assert isinstance(self.camera, OpenGLCamera)
|
||||
assert isinstance(self.renderer, OpenGLRenderer)
|
||||
if not self.check_interactive_embed_is_valid():
|
||||
return
|
||||
self.interactive_mode = True
|
||||
from IPython.terminal.embed import InteractiveShellEmbed
|
||||
|
||||
def ipython(shell, namespace):
|
||||
def ipython(shell: InteractiveShellEmbed, namespace: dict[str, Any]) -> None:
|
||||
import manim.opengl
|
||||
|
||||
def load_module_into_namespace(module, namespace):
|
||||
def load_module_into_namespace(
|
||||
module: Any, namespace: dict[str, Any]
|
||||
) -> None:
|
||||
for name in dir(module):
|
||||
namespace[name] = getattr(module, name)
|
||||
|
||||
load_module_into_namespace(manim, namespace)
|
||||
load_module_into_namespace(manim.opengl, namespace)
|
||||
|
||||
def embedded_rerun(*args, **kwargs):
|
||||
def embedded_rerun(*args: Any, **kwargs: Any) -> None:
|
||||
self.queue.put(("rerun_keyboard", args, kwargs))
|
||||
shell.exiter()
|
||||
|
||||
|
|
@ -1310,10 +1367,14 @@ class Scene:
|
|||
shell(local_ns=namespace)
|
||||
self.queue.put(("exit_keyboard", [], {}))
|
||||
|
||||
def get_embedded_method(method_name):
|
||||
return lambda *args, **kwargs: self.queue.put((method_name, args, kwargs))
|
||||
def get_embedded_method(method_name: str) -> Callable[..., None]:
|
||||
def embedded_method(*args: Any, **kwargs: Any) -> None:
|
||||
self.queue.put((method_name, args, kwargs))
|
||||
|
||||
local_namespace = inspect.currentframe().f_back.f_locals
|
||||
return embedded_method
|
||||
|
||||
currentframe: FrameType = inspect.currentframe() # type: ignore[assignment]
|
||||
local_namespace = currentframe.f_back.f_locals # type: ignore[union-attr]
|
||||
for method in ("play", "wait", "add", "remove"):
|
||||
embedded_method = get_embedded_method(method)
|
||||
# Allow for calling scene methods without prepending 'self.'.
|
||||
|
|
@ -1322,7 +1383,6 @@ class Scene:
|
|||
from sqlite3 import connect
|
||||
|
||||
from IPython.core.getipython import get_ipython
|
||||
from IPython.terminal.embed import InteractiveShellEmbed
|
||||
from traitlets.config import Config
|
||||
|
||||
cfg = Config()
|
||||
|
|
@ -1358,7 +1418,10 @@ class Scene:
|
|||
|
||||
self.interact(shell, keyboard_thread)
|
||||
|
||||
def interact(self, shell, keyboard_thread):
|
||||
# from IPython.terminal.embed import InteractiveShellEmbed
|
||||
|
||||
def interact(self, shell: Any, keyboard_thread: threading.Thread) -> None:
|
||||
assert isinstance(self.renderer, OpenGLRenderer)
|
||||
event_handler = RerunSceneHandler(self.queue)
|
||||
file_observer = Observer()
|
||||
file_observer.schedule(event_handler, config["input_file"], recursive=True)
|
||||
|
|
@ -1435,7 +1498,8 @@ class Scene:
|
|||
if self.renderer.window.is_closing:
|
||||
self.renderer.window.destroy()
|
||||
|
||||
def embed(self):
|
||||
def embed(self) -> None:
|
||||
assert isinstance(self.renderer, OpenGLRenderer)
|
||||
if not config["preview"]:
|
||||
logger.warning("Called embed() while no preview window is available.")
|
||||
return
|
||||
|
|
@ -1459,7 +1523,9 @@ class Scene:
|
|||
|
||||
# Use the locals of the caller as the local namespace
|
||||
# once embedded, and add a few custom shortcuts.
|
||||
local_ns = inspect.currentframe().f_back.f_locals
|
||||
current_frame = inspect.currentframe()
|
||||
assert isinstance(current_frame, FrameType)
|
||||
local_ns = current_frame.f_back.f_locals # type: ignore[union-attr]
|
||||
# local_ns["touch"] = self.interact
|
||||
for method in (
|
||||
"play",
|
||||
|
|
@ -1477,9 +1543,10 @@ class Scene:
|
|||
# End scene when exiting an embed.
|
||||
raise Exception("Exiting scene.")
|
||||
|
||||
def update_to_time(self, t):
|
||||
def update_to_time(self, t: float) -> None:
|
||||
dt = t - self.last_t
|
||||
self.last_t = t
|
||||
assert self.animations is not None
|
||||
for animation in self.animations:
|
||||
animation.update_mobjects(dt)
|
||||
alpha = t / animation.run_time
|
||||
|
|
@ -1541,8 +1608,8 @@ class Scene:
|
|||
sound_file: str,
|
||||
time_offset: float = 0,
|
||||
gain: float | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""
|
||||
This method is used to add a sound to the animation.
|
||||
|
||||
|
|
@ -1583,7 +1650,9 @@ class Scene:
|
|||
time = self.time + time_offset
|
||||
self.renderer.file_writer.add_sound(sound_file, time, gain, **kwargs)
|
||||
|
||||
def on_mouse_motion(self, point, d_point):
|
||||
def on_mouse_motion(self, point: Point3D, d_point: Point3D) -> None:
|
||||
assert isinstance(self.camera, OpenGLCamera)
|
||||
assert isinstance(self.renderer, OpenGLRenderer)
|
||||
self.mouse_point.move_to(point)
|
||||
if SHIFT_VALUE in self.renderer.pressed_keys:
|
||||
shift = -d_point
|
||||
|
|
@ -1593,13 +1662,15 @@ class Scene:
|
|||
shift = np.dot(np.transpose(transform), shift)
|
||||
self.camera.shift(shift)
|
||||
|
||||
def on_mouse_scroll(self, point, offset):
|
||||
def on_mouse_scroll(self, point: Point3D, offset: Point3D) -> None:
|
||||
assert isinstance(self.camera, OpenGLCamera)
|
||||
if not config.use_projection_stroke_shaders:
|
||||
factor = 1 + np.arctan(-2.1 * offset[1])
|
||||
self.camera.scale(factor, about_point=self.camera_target)
|
||||
self.mouse_scroll_orbit_controls(point, offset)
|
||||
|
||||
def on_key_press(self, symbol, modifiers):
|
||||
def on_key_press(self, symbol: int, modifiers: int) -> None:
|
||||
assert isinstance(self.camera, OpenGLCamera)
|
||||
try:
|
||||
char = chr(symbol)
|
||||
except OverflowError:
|
||||
|
|
@ -1615,10 +1686,17 @@ class Scene:
|
|||
if char in self.key_to_function_map:
|
||||
self.key_to_function_map[char]()
|
||||
|
||||
def on_key_release(self, symbol, modifiers):
|
||||
def on_key_release(self, symbol: int, modifiers: int) -> None:
|
||||
pass
|
||||
|
||||
def on_mouse_drag(self, point, d_point, buttons, modifiers):
|
||||
def on_mouse_drag(
|
||||
self,
|
||||
point: Point3D,
|
||||
d_point: Point3D,
|
||||
buttons: int,
|
||||
modifiers: int,
|
||||
) -> None:
|
||||
assert isinstance(self.camera, OpenGLCamera)
|
||||
self.mouse_drag_point.move_to(point)
|
||||
if buttons == 1:
|
||||
self.camera.increment_theta(-d_point[0])
|
||||
|
|
@ -1632,7 +1710,8 @@ class Scene:
|
|||
|
||||
self.mouse_drag_orbit_controls(point, d_point, buttons, modifiers)
|
||||
|
||||
def mouse_scroll_orbit_controls(self, point, offset):
|
||||
def mouse_scroll_orbit_controls(self, point: Point3D, offset: Point3D) -> None:
|
||||
assert isinstance(self.camera, OpenGLCamera)
|
||||
camera_to_target = self.camera_target - self.camera.get_position()
|
||||
camera_to_target *= np.sign(offset[1])
|
||||
shift_vector = 0.01 * camera_to_target
|
||||
|
|
@ -1640,7 +1719,14 @@ class Scene:
|
|||
opengl.translation_matrix(*shift_vector) @ self.camera.model_matrix
|
||||
)
|
||||
|
||||
def mouse_drag_orbit_controls(self, point, d_point, buttons, modifiers):
|
||||
def mouse_drag_orbit_controls(
|
||||
self,
|
||||
point: Point3D,
|
||||
d_point: Point3D,
|
||||
buttons: int,
|
||||
modifiers: int,
|
||||
) -> None:
|
||||
assert isinstance(self.camera, OpenGLCamera)
|
||||
# Left click drag.
|
||||
if buttons == 1:
|
||||
# Translate to target the origin and rotate around the z axis.
|
||||
|
|
@ -1713,9 +1799,9 @@ class Scene:
|
|||
)
|
||||
self.camera_target += total_shift_vector
|
||||
|
||||
def set_key_function(self, char, func):
|
||||
def set_key_function(self, char: str, func: Callable[[], Any]) -> None:
|
||||
self.key_to_function_map[char] = func
|
||||
|
||||
def on_mouse_press(self, point, button, modifiers):
|
||||
def on_mouse_press(self, point: Point3D, button: int, modifiers: int) -> None:
|
||||
for func in self.mouse_press_callbacks:
|
||||
func()
|
||||
|
|
|
|||
|
|
@ -4,10 +4,13 @@ from __future__ import annotations
|
|||
|
||||
__all__ = ["VectorScene", "LinearTransformationScene"]
|
||||
|
||||
from typing import Callable
|
||||
from collections.abc import Iterable
|
||||
from typing import TYPE_CHECKING, Any, Callable, cast
|
||||
|
||||
import numpy as np
|
||||
|
||||
from manim.animation.creation import DrawBorderThenFill, Group
|
||||
from manim.camera.camera import Camera
|
||||
from manim.mobject.geometry.arc import Dot
|
||||
from manim.mobject.geometry.line import Arrow, Line, Vector
|
||||
from manim.mobject.geometry.polygram import Rectangle
|
||||
|
|
@ -41,6 +44,14 @@ from ..utils.color import (
|
|||
from ..utils.rate_functions import rush_from, rush_into
|
||||
from ..utils.space_ops import angle_of_vector
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from manim.typing import MappingFunction, Point2DLike, Point3D, Point3DLike
|
||||
|
||||
|
||||
X_COLOR = GREEN_C
|
||||
Y_COLOR = RED_C
|
||||
Z_COLOR = BLUE_D
|
||||
|
|
@ -53,11 +64,11 @@ Z_COLOR = BLUE_D
|
|||
# Also, methods I would have thought of as getters, like coords_to_vector, are
|
||||
# actually doing a lot of animating.
|
||||
class VectorScene(Scene):
|
||||
def __init__(self, basis_vector_stroke_width=6, **kwargs):
|
||||
def __init__(self, basis_vector_stroke_width: float = 6.0, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.basis_vector_stroke_width = basis_vector_stroke_width
|
||||
|
||||
def add_plane(self, animate: bool = False, **kwargs):
|
||||
def add_plane(self, animate: bool = False, **kwargs: Any) -> NumberPlane:
|
||||
"""
|
||||
Adds a NumberPlane object to the background.
|
||||
|
||||
|
|
@ -79,7 +90,11 @@ class VectorScene(Scene):
|
|||
self.add(plane)
|
||||
return plane
|
||||
|
||||
def add_axes(self, animate: bool = False, color: bool = WHITE):
|
||||
def add_axes(
|
||||
self,
|
||||
animate: bool = False,
|
||||
color: ParsableManimColor | Iterable[ParsableManimColor] = WHITE,
|
||||
) -> Axes:
|
||||
"""
|
||||
Adds a pair of Axes to the Scene.
|
||||
|
||||
|
|
@ -96,7 +111,9 @@ class VectorScene(Scene):
|
|||
self.add(axes)
|
||||
return axes
|
||||
|
||||
def lock_in_faded_grid(self, dimness: float = 0.7, axes_dimness: float = 0.5):
|
||||
def lock_in_faded_grid(
|
||||
self, dimness: float = 0.7, axes_dimness: float = 0.5
|
||||
) -> None:
|
||||
"""
|
||||
This method freezes the NumberPlane and Axes that were already
|
||||
in the background, and adds new, manipulatable ones to the foreground.
|
||||
|
|
@ -116,11 +133,13 @@ class VectorScene(Scene):
|
|||
axes.fade(axes_dimness)
|
||||
self.add(axes)
|
||||
|
||||
self.renderer.update_frame()
|
||||
# TODO
|
||||
# error: Missing positional argument "scene" in call to "update_frame" of "CairoRenderer" [call-arg]
|
||||
self.renderer.update_frame() # type: ignore[call-arg]
|
||||
self.renderer.camera = Camera(self.renderer.get_frame())
|
||||
self.clear()
|
||||
|
||||
def get_vector(self, numerical_vector: np.ndarray | list | tuple, **kwargs):
|
||||
def get_vector(self, numerical_vector: Point3DLike, **kwargs: Any) -> Arrow:
|
||||
"""
|
||||
Returns an arrow on the Plane given an input numerical vector.
|
||||
|
||||
|
|
@ -137,19 +156,21 @@ class VectorScene(Scene):
|
|||
The Arrow representing the Vector.
|
||||
"""
|
||||
return Arrow(
|
||||
self.plane.coords_to_point(0, 0),
|
||||
self.plane.coords_to_point(*numerical_vector[:2]),
|
||||
# TODO
|
||||
# error: "VectorScene" has no attribute "plane" [attr-defined]
|
||||
self.plane.coords_to_point(0, 0), # type: ignore[attr-defined]
|
||||
self.plane.coords_to_point(*numerical_vector[:2]), # type: ignore[attr-defined]
|
||||
buff=0,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def add_vector(
|
||||
self,
|
||||
vector: Arrow | list | tuple | np.ndarray,
|
||||
color: str = YELLOW,
|
||||
vector: Arrow | Point3DLike,
|
||||
color: ParsableManimColor | Iterable[ParsableManimColor] = YELLOW,
|
||||
animate: bool = True,
|
||||
**kwargs,
|
||||
):
|
||||
**kwargs: Any,
|
||||
) -> Arrow:
|
||||
"""
|
||||
Returns the Vector after adding it to the Plane.
|
||||
|
||||
|
|
@ -179,13 +200,13 @@ class VectorScene(Scene):
|
|||
The arrow representing the vector.
|
||||
"""
|
||||
if not isinstance(vector, Arrow):
|
||||
vector = Vector(vector, color=color, **kwargs)
|
||||
vector = Vector(np.asarray(vector), color=color, **kwargs)
|
||||
if animate:
|
||||
self.play(GrowArrow(vector))
|
||||
self.add(vector)
|
||||
return vector
|
||||
|
||||
def write_vector_coordinates(self, vector: Arrow, **kwargs):
|
||||
def write_vector_coordinates(self, vector: Vector, **kwargs: Any) -> Matrix:
|
||||
"""
|
||||
Returns a column matrix indicating the vector coordinates,
|
||||
after writing them to the screen.
|
||||
|
|
@ -203,11 +224,15 @@ class VectorScene(Scene):
|
|||
:class:`.Matrix`
|
||||
The column matrix representing the vector.
|
||||
"""
|
||||
coords = vector.coordinate_label(**kwargs)
|
||||
coords: Matrix = vector.coordinate_label(**kwargs)
|
||||
self.play(Write(coords))
|
||||
return coords
|
||||
|
||||
def get_basis_vectors(self, i_hat_color: str = X_COLOR, j_hat_color: str = Y_COLOR):
|
||||
def get_basis_vectors(
|
||||
self,
|
||||
i_hat_color: ParsableManimColor | Iterable[ParsableManimColor] = X_COLOR,
|
||||
j_hat_color: ParsableManimColor | Iterable[ParsableManimColor] = Y_COLOR,
|
||||
) -> VGroup:
|
||||
"""
|
||||
Returns a VGroup of the Basis Vectors (1,0) and (0,1)
|
||||
|
||||
|
|
@ -226,12 +251,16 @@ class VectorScene(Scene):
|
|||
"""
|
||||
return VGroup(
|
||||
*(
|
||||
Vector(vect, color=color, stroke_width=self.basis_vector_stroke_width)
|
||||
Vector(
|
||||
np.asarray(vect),
|
||||
color=color,
|
||||
stroke_width=self.basis_vector_stroke_width,
|
||||
)
|
||||
for vect, color in [([1, 0], i_hat_color), ([0, 1], j_hat_color)]
|
||||
)
|
||||
)
|
||||
|
||||
def get_basis_vector_labels(self, **kwargs):
|
||||
def get_basis_vector_labels(self, **kwargs: Any) -> VGroup:
|
||||
"""
|
||||
Returns naming labels for the basis vectors.
|
||||
|
||||
|
|
@ -263,13 +292,13 @@ class VectorScene(Scene):
|
|||
def get_vector_label(
|
||||
self,
|
||||
vector: Vector,
|
||||
label,
|
||||
label: MathTex | str,
|
||||
at_tip: bool = False,
|
||||
direction: str = "left",
|
||||
rotate: bool = False,
|
||||
color: str | None = None,
|
||||
color: ParsableManimColor | None = None,
|
||||
label_scale_factor: float = LARGE_BUFF - 0.2,
|
||||
):
|
||||
) -> MathTex:
|
||||
"""
|
||||
Returns naming labels for the passed vector.
|
||||
|
||||
|
|
@ -300,8 +329,11 @@ class VectorScene(Scene):
|
|||
label = "\\vec{\\textbf{%s}}" % label # noqa: UP031
|
||||
label = MathTex(label)
|
||||
if color is None:
|
||||
color = vector.get_color()
|
||||
label.set_color(color)
|
||||
prepared_color: ParsableManimColor = vector.get_color()
|
||||
else:
|
||||
prepared_color = color
|
||||
label.set_color(prepared_color)
|
||||
assert isinstance(label, MathTex)
|
||||
label.scale(label_scale_factor)
|
||||
label.add_background_rectangle()
|
||||
|
||||
|
|
@ -314,16 +346,18 @@ class VectorScene(Scene):
|
|||
if not rotate:
|
||||
label.rotate(-angle, about_point=ORIGIN)
|
||||
if direction == "left":
|
||||
label.shift(-label.get_bottom() + 0.1 * UP)
|
||||
temp_shift_1: Point3D = np.asarray(label.get_bottom())
|
||||
label.shift(-temp_shift_1 + 0.1 * UP)
|
||||
else:
|
||||
label.shift(-label.get_top() + 0.1 * DOWN)
|
||||
temp_shift_2: Point3D = np.asarray(label.get_top())
|
||||
label.shift(-temp_shift_2 + 0.1 * DOWN)
|
||||
label.rotate(angle, about_point=ORIGIN)
|
||||
label.shift((vector.get_end() - vector.get_start()) / 2)
|
||||
return label
|
||||
|
||||
def label_vector(
|
||||
self, vector: Vector, label: MathTex | str, animate: bool = True, **kwargs
|
||||
):
|
||||
self, vector: Vector, label: MathTex | str, animate: bool = True, **kwargs: Any
|
||||
) -> MathTex:
|
||||
"""
|
||||
Shortcut method for creating, and animating the addition of
|
||||
a label for the vector.
|
||||
|
|
@ -347,38 +381,38 @@ class VectorScene(Scene):
|
|||
:class:`~.MathTex`
|
||||
The MathTex of the label.
|
||||
"""
|
||||
label = self.get_vector_label(vector, label, **kwargs)
|
||||
mathtex_label = self.get_vector_label(vector, label, **kwargs)
|
||||
if animate:
|
||||
self.play(Write(label, run_time=1))
|
||||
self.add(label)
|
||||
return label
|
||||
self.play(Write(mathtex_label, run_time=1))
|
||||
self.add(mathtex_label)
|
||||
return mathtex_label
|
||||
|
||||
def position_x_coordinate(
|
||||
self,
|
||||
x_coord,
|
||||
x_line,
|
||||
vector,
|
||||
): # TODO Write DocStrings for this.
|
||||
x_coord: MathTex,
|
||||
x_line: Line,
|
||||
vector: Point3DLike,
|
||||
) -> MathTex: # TODO Write DocStrings for this.
|
||||
x_coord.next_to(x_line, -np.sign(vector[1]) * UP)
|
||||
x_coord.set_color(X_COLOR)
|
||||
return x_coord
|
||||
|
||||
def position_y_coordinate(
|
||||
self,
|
||||
y_coord,
|
||||
y_line,
|
||||
vector,
|
||||
): # TODO Write DocStrings for this.
|
||||
y_coord: MathTex,
|
||||
y_line: Line,
|
||||
vector: Point3DLike,
|
||||
) -> MathTex: # TODO Write DocStrings for this.
|
||||
y_coord.next_to(y_line, np.sign(vector[0]) * RIGHT)
|
||||
y_coord.set_color(Y_COLOR)
|
||||
return y_coord
|
||||
|
||||
def coords_to_vector(
|
||||
self,
|
||||
vector: np.ndarray | list | tuple,
|
||||
coords_start: np.ndarray | list | tuple = 2 * RIGHT + 2 * UP,
|
||||
vector: Point2DLike,
|
||||
coords_start: Point3DLike = 2 * RIGHT + 2 * UP,
|
||||
clean_up: bool = True,
|
||||
):
|
||||
) -> None:
|
||||
"""
|
||||
This method writes the vector as a column matrix (henceforth called the label),
|
||||
takes the values in it one by one, and form the corresponding
|
||||
|
|
@ -409,26 +443,29 @@ class VectorScene(Scene):
|
|||
y_line = Line(x_line.get_end(), arrow.get_end())
|
||||
x_line.set_color(X_COLOR)
|
||||
y_line.set_color(Y_COLOR)
|
||||
x_coord, y_coord = array.get_mob_matrix().flatten()
|
||||
mob_matrix = array.get_mob_matrix()
|
||||
x_coord = mob_matrix[0][0]
|
||||
y_coord = mob_matrix[1][0]
|
||||
|
||||
self.play(Write(array, run_time=1))
|
||||
self.wait()
|
||||
self.play(
|
||||
ApplyFunction(
|
||||
lambda x: self.position_x_coordinate(x, x_line, vector),
|
||||
lambda x: self.position_x_coordinate(x, x_line, vector), # type: ignore[arg-type]
|
||||
x_coord,
|
||||
),
|
||||
)
|
||||
self.play(Create(x_line))
|
||||
animations = [
|
||||
ApplyFunction(
|
||||
lambda y: self.position_y_coordinate(y, y_line, vector),
|
||||
lambda y: self.position_y_coordinate(y, y_line, vector), # type: ignore[arg-type]
|
||||
y_coord,
|
||||
),
|
||||
FadeOut(array.get_brackets()),
|
||||
]
|
||||
self.play(*animations)
|
||||
y_coord, _ = (anim.mobject for anim in animations)
|
||||
# TODO: Can we delete the line below? I don't think it have any purpose.
|
||||
# y_coord, _ = (anim.mobject for anim in animations)
|
||||
self.play(Create(y_line))
|
||||
self.play(Create(arrow))
|
||||
self.wait()
|
||||
|
|
@ -438,10 +475,10 @@ class VectorScene(Scene):
|
|||
|
||||
def vector_to_coords(
|
||||
self,
|
||||
vector: np.ndarray | list | tuple,
|
||||
vector: Point3DLike,
|
||||
integer_labels: bool = True,
|
||||
clean_up: bool = True,
|
||||
):
|
||||
) -> tuple[Matrix, Line, Line]:
|
||||
"""
|
||||
This method displays vector as a Vector() based vector, and then shows
|
||||
the corresponding lines that make up the x and y components of the vector.
|
||||
|
|
@ -475,7 +512,7 @@ class VectorScene(Scene):
|
|||
y_line = Line(x_line.get_end(), arrow.get_end())
|
||||
x_line.set_color(X_COLOR)
|
||||
y_line.set_color(Y_COLOR)
|
||||
x_coord, y_coord = array.get_entries()
|
||||
x_coord, y_coord = cast(VGroup, array.get_entries())
|
||||
x_coord_start = self.position_x_coordinate(x_coord.copy(), x_line, vector)
|
||||
y_coord_start = self.position_y_coordinate(y_coord.copy(), y_line, vector)
|
||||
brackets = array.get_brackets()
|
||||
|
|
@ -499,7 +536,7 @@ class VectorScene(Scene):
|
|||
self.add(*starting_mobjects)
|
||||
return array, x_line, y_line
|
||||
|
||||
def show_ghost_movement(self, vector: Arrow | list | tuple | np.ndarray):
|
||||
def show_ghost_movement(self, vector: Arrow | Point2DLike | Point3DLike) -> None:
|
||||
"""
|
||||
This method plays an animation that partially shows the entire plane moving
|
||||
in the direction of a particular vector. This is useful when you wish to
|
||||
|
|
@ -513,20 +550,26 @@ class VectorScene(Scene):
|
|||
"""
|
||||
if isinstance(vector, Arrow):
|
||||
vector = vector.get_end() - vector.get_start()
|
||||
elif len(vector) == 2:
|
||||
vector = np.append(np.array(vector), 0.0)
|
||||
x_max = int(config["frame_x_radius"] + abs(vector[0]))
|
||||
y_max = int(config["frame_y_radius"] + abs(vector[1]))
|
||||
else:
|
||||
vector = np.asarray(vector)
|
||||
if len(vector) == 2:
|
||||
vector = np.append(np.array(vector), 0.0)
|
||||
vector_cleaned: Point3D = vector
|
||||
|
||||
x_max = int(config["frame_x_radius"] + abs(vector_cleaned[0]))
|
||||
y_max = int(config["frame_y_radius"] + abs(vector_cleaned[1]))
|
||||
# TODO:
|
||||
# I think that this should be a VGroup instead of a VMobject.
|
||||
dots = VMobject(
|
||||
*(
|
||||
*( # type: ignore[arg-type]
|
||||
Dot(x * RIGHT + y * UP)
|
||||
for x in range(-x_max, x_max)
|
||||
for y in range(-y_max, y_max)
|
||||
)
|
||||
)
|
||||
dots.set_fill(BLACK, opacity=0)
|
||||
dots_halfway = dots.copy().shift(vector / 2).set_fill(WHITE, 1)
|
||||
dots_end = dots.copy().shift(vector)
|
||||
dots_halfway = dots.copy().shift(vector_cleaned / 2).set_fill(WHITE, 1)
|
||||
dots_end = dots.copy().shift(vector_cleaned)
|
||||
|
||||
self.play(Transform(dots, dots_halfway, rate_func=rush_into))
|
||||
self.play(Transform(dots, dots_end, rate_func=rush_from))
|
||||
|
|
@ -585,16 +628,16 @@ class LinearTransformationScene(VectorScene):
|
|||
self,
|
||||
include_background_plane: bool = True,
|
||||
include_foreground_plane: bool = True,
|
||||
background_plane_kwargs: dict | None = None,
|
||||
foreground_plane_kwargs: dict | None = None,
|
||||
background_plane_kwargs: dict[str, Any] | None = None,
|
||||
foreground_plane_kwargs: dict[str, Any] | None = None,
|
||||
show_coordinates: bool = False,
|
||||
show_basis_vectors: bool = True,
|
||||
basis_vector_stroke_width: float = 6,
|
||||
i_hat_color: ParsableManimColor = X_COLOR,
|
||||
j_hat_color: ParsableManimColor = Y_COLOR,
|
||||
leave_ghost_vectors: bool = False,
|
||||
**kwargs,
|
||||
):
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.include_background_plane = include_background_plane
|
||||
|
|
@ -605,7 +648,7 @@ class LinearTransformationScene(VectorScene):
|
|||
self.i_hat_color = ManimColor(i_hat_color)
|
||||
self.j_hat_color = ManimColor(j_hat_color)
|
||||
self.leave_ghost_vectors = leave_ghost_vectors
|
||||
self.background_plane_kwargs = {
|
||||
self.background_plane_kwargs: dict[str, Any] = {
|
||||
"color": GREY,
|
||||
"axis_config": {
|
||||
"color": GREY,
|
||||
|
|
@ -618,7 +661,7 @@ class LinearTransformationScene(VectorScene):
|
|||
|
||||
self.ghost_vectors = VGroup()
|
||||
|
||||
self.foreground_plane_kwargs = {
|
||||
self.foreground_plane_kwargs: dict[str, Any] = {
|
||||
"x_range": np.array([-config["frame_width"], config["frame_width"], 1.0]),
|
||||
"y_range": np.array([-config["frame_width"], config["frame_width"], 1.0]),
|
||||
"faded_line_ratio": 1,
|
||||
|
|
@ -630,22 +673,25 @@ class LinearTransformationScene(VectorScene):
|
|||
)
|
||||
|
||||
@staticmethod
|
||||
def update_default_configs(default_configs, passed_configs):
|
||||
def update_default_configs(
|
||||
default_configs: Iterable[dict[str, Any]],
|
||||
passed_configs: Iterable[dict[str, Any] | None],
|
||||
) -> None:
|
||||
for default_config, passed_config in zip(default_configs, passed_configs):
|
||||
if passed_config is not None:
|
||||
update_dict_recursively(default_config, passed_config)
|
||||
|
||||
def setup(self):
|
||||
def setup(self) -> None:
|
||||
# The has_already_setup attr is to not break all the old Scenes
|
||||
if hasattr(self, "has_already_setup"):
|
||||
return
|
||||
self.has_already_setup = True
|
||||
self.background_mobjects = []
|
||||
self.foreground_mobjects = []
|
||||
self.transformable_mobjects = []
|
||||
self.moving_vectors = []
|
||||
self.transformable_labels = []
|
||||
self.moving_mobjects = []
|
||||
self.background_mobjects: list[Mobject] = []
|
||||
self.foreground_mobjects: list[Mobject] = []
|
||||
self.transformable_mobjects: list[Mobject] = []
|
||||
self.moving_vectors: list[Mobject] = []
|
||||
self.transformable_labels: list[MathTex] = []
|
||||
self.moving_mobjects: list[Mobject] = []
|
||||
|
||||
self.background_plane = NumberPlane(**self.background_plane_kwargs)
|
||||
|
||||
|
|
@ -665,7 +711,9 @@ class LinearTransformationScene(VectorScene):
|
|||
self.i_hat, self.j_hat = self.basis_vectors
|
||||
self.add(self.basis_vectors)
|
||||
|
||||
def add_special_mobjects(self, mob_list: list, *mobs_to_add: Mobject):
|
||||
def add_special_mobjects(
|
||||
self, mob_list: list[Mobject], *mobs_to_add: Mobject
|
||||
) -> None:
|
||||
"""
|
||||
Adds mobjects to a separate list that can be tracked,
|
||||
if these mobjects have some extra importance.
|
||||
|
|
@ -685,7 +733,7 @@ class LinearTransformationScene(VectorScene):
|
|||
mob_list.append(mobject)
|
||||
self.add(mobject)
|
||||
|
||||
def add_background_mobject(self, *mobjects: Mobject):
|
||||
def add_background_mobject(self, *mobjects: Mobject) -> None:
|
||||
"""
|
||||
Adds the mobjects to the special list
|
||||
self.background_mobjects.
|
||||
|
|
@ -697,8 +745,9 @@ class LinearTransformationScene(VectorScene):
|
|||
"""
|
||||
self.add_special_mobjects(self.background_mobjects, *mobjects)
|
||||
|
||||
# TODO, this conflicts with Scene.add_fore
|
||||
def add_foreground_mobject(self, *mobjects: Mobject):
|
||||
# TODO, this conflicts with Scene.add_foreground_mobject
|
||||
# Please be aware that there is also the method Scene.add_foreground_mobjects.
|
||||
def add_foreground_mobject(self, *mobjects: Mobject) -> None: # type: ignore[override]
|
||||
"""
|
||||
Adds the mobjects to the special list
|
||||
self.foreground_mobjects.
|
||||
|
|
@ -710,7 +759,7 @@ class LinearTransformationScene(VectorScene):
|
|||
"""
|
||||
self.add_special_mobjects(self.foreground_mobjects, *mobjects)
|
||||
|
||||
def add_transformable_mobject(self, *mobjects: Mobject):
|
||||
def add_transformable_mobject(self, *mobjects: Mobject) -> None:
|
||||
"""
|
||||
Adds the mobjects to the special list
|
||||
self.transformable_mobjects.
|
||||
|
|
@ -724,7 +773,7 @@ class LinearTransformationScene(VectorScene):
|
|||
|
||||
def add_moving_mobject(
|
||||
self, mobject: Mobject, target_mobject: Mobject | None = None
|
||||
):
|
||||
) -> None:
|
||||
"""
|
||||
Adds the mobject to the special list
|
||||
self.moving_mobject, and adds a property
|
||||
|
|
@ -751,8 +800,11 @@ class LinearTransformationScene(VectorScene):
|
|||
return self.ghost_vectors
|
||||
|
||||
def get_unit_square(
|
||||
self, color: str = YELLOW, opacity: float = 0.3, stroke_width: float = 3
|
||||
):
|
||||
self,
|
||||
color: ParsableManimColor | Iterable[ParsableManimColor] = YELLOW,
|
||||
opacity: float = 0.3,
|
||||
stroke_width: float = 3,
|
||||
) -> Rectangle:
|
||||
"""
|
||||
Returns a unit square for the current NumberPlane.
|
||||
|
||||
|
|
@ -783,7 +835,7 @@ class LinearTransformationScene(VectorScene):
|
|||
square.move_to(self.plane.coords_to_point(0, 0), DL)
|
||||
return square
|
||||
|
||||
def add_unit_square(self, animate: bool = False, **kwargs):
|
||||
def add_unit_square(self, animate: bool = False, **kwargs: Any) -> Self:
|
||||
"""
|
||||
Adds a unit square to the scene via
|
||||
self.get_unit_square.
|
||||
|
|
@ -814,8 +866,12 @@ class LinearTransformationScene(VectorScene):
|
|||
return self
|
||||
|
||||
def add_vector(
|
||||
self, vector: Arrow | list | tuple | np.ndarray, color: str = YELLOW, **kwargs
|
||||
):
|
||||
self,
|
||||
vector: Arrow | list | tuple | np.ndarray,
|
||||
color: ParsableManimColor = YELLOW,
|
||||
animate: bool = False,
|
||||
**kwargs: Any,
|
||||
) -> Arrow:
|
||||
"""
|
||||
Adds a vector to the scene, and puts it in the special
|
||||
list self.moving_vectors.
|
||||
|
|
@ -839,11 +895,11 @@ class LinearTransformationScene(VectorScene):
|
|||
Arrow
|
||||
The arrow representing the vector.
|
||||
"""
|
||||
vector = super().add_vector(vector, color=color, **kwargs)
|
||||
vector = super().add_vector(vector, color=color, animate=animate, **kwargs)
|
||||
self.moving_vectors.append(vector)
|
||||
return vector
|
||||
|
||||
def write_vector_coordinates(self, vector: Arrow, **kwargs):
|
||||
def write_vector_coordinates(self, vector: Vector, **kwargs: Any) -> Matrix:
|
||||
"""
|
||||
Returns a column matrix indicating the vector coordinates,
|
||||
after writing them to the screen, and adding them to the
|
||||
|
|
@ -872,8 +928,8 @@ class LinearTransformationScene(VectorScene):
|
|||
label: MathTex | str,
|
||||
transformation_name: str | MathTex = "L",
|
||||
new_label: str | MathTex | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
**kwargs: Any,
|
||||
) -> MathTex:
|
||||
"""
|
||||
Method for creating, and animating the addition of
|
||||
a transformable label for the vector.
|
||||
|
|
@ -900,26 +956,27 @@ class LinearTransformationScene(VectorScene):
|
|||
:class:`~.MathTex`
|
||||
The MathTex of the label.
|
||||
"""
|
||||
# TODO: Clear up types in this function. This is currently a mess.
|
||||
label_mob = self.label_vector(vector, label, **kwargs)
|
||||
if new_label:
|
||||
label_mob.target_text = new_label
|
||||
label_mob.target_text = new_label # type: ignore[attr-defined]
|
||||
else:
|
||||
label_mob.target_text = (
|
||||
f"{transformation_name}({label_mob.get_tex_string()})"
|
||||
label_mob.target_text = ( # type: ignore[attr-defined]
|
||||
f"{transformation_name}({label_mob.get_tex_string()})" # type: ignore[no-untyped-call]
|
||||
)
|
||||
label_mob.vector = vector
|
||||
label_mob.kwargs = kwargs
|
||||
if "animate" in label_mob.kwargs:
|
||||
label_mob.kwargs.pop("animate")
|
||||
label_mob.vector = vector # type: ignore[attr-defined]
|
||||
label_mob.kwargs = kwargs # type: ignore[attr-defined]
|
||||
if "animate" in label_mob.kwargs: # type: ignore[attr-defined]
|
||||
label_mob.kwargs.pop("animate") # type: ignore[attr-defined]
|
||||
self.transformable_labels.append(label_mob)
|
||||
return label_mob
|
||||
return cast(MathTex, label_mob)
|
||||
|
||||
def add_title(
|
||||
self,
|
||||
title: str | MathTex | Tex,
|
||||
scale_factor: float = 1.5,
|
||||
animate: bool = False,
|
||||
):
|
||||
) -> Self:
|
||||
"""
|
||||
Adds a title, after scaling it, adding a background rectangle,
|
||||
moving it to the top and adding it to foreground_mobjects adding
|
||||
|
|
@ -951,7 +1008,9 @@ class LinearTransformationScene(VectorScene):
|
|||
self.title = title
|
||||
return self
|
||||
|
||||
def get_matrix_transformation(self, matrix: np.ndarray | list | tuple):
|
||||
def get_matrix_transformation(
|
||||
self, matrix: np.ndarray | list | tuple
|
||||
) -> Callable[[Point3D], Point3D]:
|
||||
"""
|
||||
Returns a function corresponding to the linear
|
||||
transformation represented by the matrix passed.
|
||||
|
|
@ -965,7 +1024,7 @@ class LinearTransformationScene(VectorScene):
|
|||
|
||||
def get_transposed_matrix_transformation(
|
||||
self, transposed_matrix: np.ndarray | list | tuple
|
||||
):
|
||||
) -> Callable[[Point3D], Point3D]:
|
||||
"""
|
||||
Returns a function corresponding to the linear
|
||||
transformation represented by the transposed
|
||||
|
|
@ -985,7 +1044,7 @@ class LinearTransformationScene(VectorScene):
|
|||
raise ValueError("Matrix has bad dimensions")
|
||||
return lambda point: np.dot(point, transposed_matrix)
|
||||
|
||||
def get_piece_movement(self, pieces: list | tuple | np.ndarray):
|
||||
def get_piece_movement(self, pieces: Iterable[Mobject]) -> Transform:
|
||||
"""
|
||||
This method returns an animation that moves an arbitrary
|
||||
mobject in "pieces" to its corresponding .target value.
|
||||
|
|
@ -1013,7 +1072,7 @@ class LinearTransformationScene(VectorScene):
|
|||
self.add(self.ghost_vectors[-1])
|
||||
return Transform(start, target, lag_ratio=0)
|
||||
|
||||
def get_moving_mobject_movement(self, func: Callable[[np.ndarray], np.ndarray]):
|
||||
def get_moving_mobject_movement(self, func: MappingFunction) -> Transform:
|
||||
"""
|
||||
This method returns an animation that moves a mobject
|
||||
in "self.moving_mobjects" to its corresponding .target value.
|
||||
|
|
@ -1034,11 +1093,12 @@ class LinearTransformationScene(VectorScene):
|
|||
for m in self.moving_mobjects:
|
||||
if m.target is None:
|
||||
m.target = m.copy()
|
||||
target_point = func(m.get_center())
|
||||
temp: Point3D = m.get_center()
|
||||
target_point = func(temp)
|
||||
m.target.move_to(target_point)
|
||||
return self.get_piece_movement(self.moving_mobjects)
|
||||
|
||||
def get_vector_movement(self, func: Callable[[np.ndarray], np.ndarray]):
|
||||
def get_vector_movement(self, func: MappingFunction) -> Transform:
|
||||
"""
|
||||
This method returns an animation that moves a mobject
|
||||
in "self.moving_vectors" to its corresponding .target value.
|
||||
|
|
@ -1058,12 +1118,12 @@ class LinearTransformationScene(VectorScene):
|
|||
"""
|
||||
for v in self.moving_vectors:
|
||||
v.target = Vector(func(v.get_end()), color=v.get_color())
|
||||
norm = np.linalg.norm(v.target.get_end())
|
||||
norm = float(np.linalg.norm(v.target.get_end()))
|
||||
if norm < 0.1:
|
||||
v.target.get_tip().scale(norm)
|
||||
return self.get_piece_movement(self.moving_vectors)
|
||||
|
||||
def get_transformable_label_movement(self):
|
||||
def get_transformable_label_movement(self) -> Transform:
|
||||
"""
|
||||
This method returns an animation that moves all labels
|
||||
in "self.transformable_labels" to its corresponding .target .
|
||||
|
|
@ -1074,12 +1134,17 @@ class LinearTransformationScene(VectorScene):
|
|||
The animation of the movement.
|
||||
"""
|
||||
for label in self.transformable_labels:
|
||||
# TODO: This location and lines 933 and 335 are the only locations in
|
||||
# the code where the target_text property is referenced.
|
||||
target_text: MathTex | str = label.target_text # type: ignore[assignment]
|
||||
label.target = self.get_vector_label(
|
||||
label.vector.target, label.target_text, **label.kwargs
|
||||
label.vector.target, # type: ignore[attr-defined]
|
||||
target_text,
|
||||
**label.kwargs, # type: ignore[arg-type]
|
||||
)
|
||||
return self.get_piece_movement(self.transformable_labels)
|
||||
|
||||
def apply_matrix(self, matrix: np.ndarray | list | tuple, **kwargs):
|
||||
def apply_matrix(self, matrix: np.ndarray | list | tuple, **kwargs: Any) -> None:
|
||||
"""
|
||||
Applies the transformation represented by the
|
||||
given matrix to the number plane, and each vector/similar
|
||||
|
|
@ -1094,7 +1159,7 @@ class LinearTransformationScene(VectorScene):
|
|||
"""
|
||||
self.apply_transposed_matrix(np.array(matrix).T, **kwargs)
|
||||
|
||||
def apply_inverse(self, matrix: np.ndarray | list | tuple, **kwargs):
|
||||
def apply_inverse(self, matrix: np.ndarray | list | tuple, **kwargs: Any) -> None:
|
||||
"""
|
||||
This method applies the linear transformation
|
||||
represented by the inverse of the passed matrix
|
||||
|
|
@ -1110,8 +1175,8 @@ class LinearTransformationScene(VectorScene):
|
|||
self.apply_matrix(np.linalg.inv(matrix), **kwargs)
|
||||
|
||||
def apply_transposed_matrix(
|
||||
self, transposed_matrix: np.ndarray | list | tuple, **kwargs
|
||||
):
|
||||
self, transposed_matrix: np.ndarray | list | tuple, **kwargs: Any
|
||||
) -> None:
|
||||
"""
|
||||
Applies the transformation represented by the
|
||||
given transposed matrix to the number plane,
|
||||
|
|
@ -1132,7 +1197,9 @@ class LinearTransformationScene(VectorScene):
|
|||
kwargs["path_arc"] = net_rotation
|
||||
self.apply_function(func, **kwargs)
|
||||
|
||||
def apply_inverse_transpose(self, t_matrix: np.ndarray | list | tuple, **kwargs):
|
||||
def apply_inverse_transpose(
|
||||
self, t_matrix: np.ndarray | list | tuple, **kwargs: Any
|
||||
) -> None:
|
||||
"""
|
||||
Applies the inverse of the transformation represented
|
||||
by the given transposed matrix to the number plane and each
|
||||
|
|
@ -1149,8 +1216,8 @@ class LinearTransformationScene(VectorScene):
|
|||
self.apply_transposed_matrix(t_inv, **kwargs)
|
||||
|
||||
def apply_nonlinear_transformation(
|
||||
self, function: Callable[[np.ndarray], np.ndarray], **kwargs
|
||||
):
|
||||
self, function: Callable[[np.ndarray], np.ndarray], **kwargs: Any
|
||||
) -> None:
|
||||
"""
|
||||
Applies the non-linear transformation represented
|
||||
by the given function to the number plane and each
|
||||
|
|
@ -1168,10 +1235,10 @@ class LinearTransformationScene(VectorScene):
|
|||
|
||||
def apply_function(
|
||||
self,
|
||||
function: Callable[[np.ndarray], np.ndarray],
|
||||
added_anims: list = [],
|
||||
**kwargs,
|
||||
):
|
||||
function: MappingFunction,
|
||||
added_anims: list[Animation] = [],
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""
|
||||
Applies the given function to each of the mobjects in
|
||||
self.transformable_mobjects, and plays the animation showing
|
||||
|
|
@ -1194,7 +1261,7 @@ class LinearTransformationScene(VectorScene):
|
|||
kwargs["run_time"] = 3
|
||||
anims = (
|
||||
[
|
||||
ApplyPointwiseFunction(function, t_mob)
|
||||
ApplyPointwiseFunction(function, t_mob) # type: ignore[arg-type]
|
||||
for t_mob in self.transformable_mobjects
|
||||
]
|
||||
+ [
|
||||
|
|
|
|||
9
mypy.ini
9
mypy.ini
|
|
@ -210,18 +210,15 @@ ignore_errors = True
|
|||
[mypy-manim.renderer.vectorized_mobject_rendering]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.scene.moving_camera_scene]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.scene.scene_file_writer]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.scene.scene]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.scene.three_d_scene]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.scene.vector_space_scene]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.scene.zoomed_scene]
|
||||
ignore_errors = True
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue