Skip to content

Commit 6b66878

Browse files
Add type annotations to moving_camera.py (#4397)
1 parent dd91a8a commit 6b66878

File tree

2 files changed

+79
-59
lines changed

2 files changed

+79
-59
lines changed

manim/camera/moving_camera.py

Lines changed: 79 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
from collections.abc import Iterable
1313
from typing import Any
1414

15-
import numpy as np
15+
from cairo import Context
16+
17+
from manim.typing import PixelArray, Point3D, Point3DLike
1618

1719
from .. import config
1820
from ..camera.camera import Camera
@@ -34,12 +36,12 @@ class MovingCamera(Camera):
3436

3537
def __init__(
3638
self,
37-
frame=None,
39+
frame: Mobject | None = None,
3840
fixed_dimension: int = 0, # width
3941
default_frame_stroke_color: ManimColor = WHITE,
4042
default_frame_stroke_width: int = 0,
4143
**kwargs: Any,
42-
) -> None:
44+
):
4345
"""Frame is a Mobject, (should almost certainly be a rectangle)
4446
determining which region of space the camera displays
4547
"""
@@ -57,7 +59,7 @@ def __init__(
5759

5860
# TODO, make these work for a rotated frame
5961
@property
60-
def frame_height(self):
62+
def frame_height(self) -> float:
6163
"""Returns the height of the frame.
6264
6365
Returns
@@ -67,30 +69,8 @@ def frame_height(self):
6769
"""
6870
return self.frame.height
6971

70-
@property
71-
def frame_width(self):
72-
"""Returns the width of the frame
73-
74-
Returns
75-
-------
76-
float
77-
The width of the frame.
78-
"""
79-
return self.frame.width
80-
81-
@property
82-
def frame_center(self):
83-
"""Returns the centerpoint of the frame in cartesian coordinates.
84-
85-
Returns
86-
-------
87-
np.array
88-
The cartesian coordinates of the center of the frame.
89-
"""
90-
return self.frame.get_center()
91-
9272
@frame_height.setter
93-
def frame_height(self, frame_height: float):
73+
def frame_height(self, frame_height: float) -> None:
9474
"""Sets the height of the frame in MUnits.
9575
9676
Parameters
@@ -100,8 +80,19 @@ def frame_height(self, frame_height: float):
10080
"""
10181
self.frame.stretch_to_fit_height(frame_height)
10282

83+
@property
84+
def frame_width(self) -> float:
85+
"""Returns the width of the frame
86+
87+
Returns
88+
-------
89+
float
90+
The width of the frame.
91+
"""
92+
return self.frame.width
93+
10394
@frame_width.setter
104-
def frame_width(self, frame_width: float):
95+
def frame_width(self, frame_width: float) -> None:
10596
"""Sets the width of the frame in MUnits.
10697
10798
Parameters
@@ -111,8 +102,19 @@ def frame_width(self, frame_width: float):
111102
"""
112103
self.frame.stretch_to_fit_width(frame_width)
113104

105+
@property
106+
def frame_center(self) -> Point3D:
107+
"""Returns the centerpoint of the frame in cartesian coordinates.
108+
109+
Returns
110+
-------
111+
np.array
112+
The cartesian coordinates of the center of the frame.
113+
"""
114+
return self.frame.get_center()
115+
114116
@frame_center.setter
115-
def frame_center(self, frame_center: np.ndarray | list | tuple | Mobject):
117+
def frame_center(self, frame_center: Point3DLike | Mobject) -> None:
116118
"""Sets the centerpoint of the frame.
117119
118120
Parameters
@@ -129,17 +131,14 @@ def capture_mobjects(self, mobjects: Iterable[Mobject], **kwargs: Any) -> None:
129131
# self.realign_frame_shape()
130132
super().capture_mobjects(mobjects, **kwargs)
131133

132-
# Since the frame can be moving around, the cairo
133-
# context used for updating should be regenerated
134-
# at each frame. So no caching.
135-
def get_cached_cairo_context(self, pixel_array):
134+
def get_cached_cairo_context(self, pixel_array: PixelArray) -> None:
136135
"""Since the frame can be moving around, the cairo
137136
context used for updating should be regenerated
138137
at each frame. So no caching.
139138
"""
140139
return None
141140

142-
def cache_cairo_context(self, pixel_array, ctx):
141+
def cache_cairo_context(self, pixel_array: PixelArray, ctx: Context) -> None:
143142
"""Since the frame can be moving around, the cairo
144143
context used for updating should be regenerated
145144
at each frame. So no caching.
@@ -157,23 +156,23 @@ def cache_cairo_context(self, pixel_array, ctx):
157156
# self.frame_shape = (self.frame.height, width)
158157
# self.resize_frame_shape(fixed_dimension=self.fixed_dimension)
159158

160-
def get_mobjects_indicating_movement(self):
159+
def get_mobjects_indicating_movement(self) -> list[Mobject]:
161160
"""Returns all mobjects whose movement implies that the camera
162161
should think of all other mobjects on the screen as moving
163162
164163
Returns
165164
-------
166-
list
165+
list[Mobject]
167166
"""
168167
return [self.frame]
169168

170169
def auto_zoom(
171170
self,
172-
mobjects: list[Mobject],
171+
mobjects: Iterable[Mobject],
173172
margin: float = 0,
174173
only_mobjects_in_frame: bool = False,
175174
animate: bool = True,
176-
):
175+
) -> Mobject:
177176
"""Zooms on to a given array of mobjects (or a singular mobject)
178177
and automatically resizes to frame all the mobjects.
179178
@@ -203,10 +202,36 @@ def auto_zoom(
203202
or ScreenRectangle with position and size updated to zoomed position.
204203
205204
"""
206-
scene_critical_x_left = None
207-
scene_critical_x_right = None
208-
scene_critical_y_up = None
209-
scene_critical_y_down = None
205+
(
206+
scene_critical_x_left,
207+
scene_critical_x_right,
208+
scene_critical_y_up,
209+
scene_critical_y_down,
210+
) = self._get_bounding_box(mobjects, only_mobjects_in_frame)
211+
212+
# calculate center x and y
213+
x = (scene_critical_x_left + scene_critical_x_right) / 2
214+
y = (scene_critical_y_up + scene_critical_y_down) / 2
215+
216+
# calculate proposed width and height of zoomed scene
217+
new_width = abs(scene_critical_x_left - scene_critical_x_right)
218+
new_height = abs(scene_critical_y_up - scene_critical_y_down)
219+
220+
m_target = self.frame.animate if animate else self.frame
221+
# zoom to fit all mobjects along the side that has the largest size
222+
if new_width / self.frame.width > new_height / self.frame.height:
223+
return m_target.set_x(x).set_y(y).set(width=new_width + margin)
224+
else:
225+
return m_target.set_x(x).set_y(y).set(height=new_height + margin)
226+
227+
def _get_bounding_box(
228+
self, mobjects: Iterable[Mobject], only_mobjects_in_frame: bool
229+
) -> tuple[float, float, float, float]:
230+
bounding_box_located = False
231+
scene_critical_x_left: float = 0
232+
scene_critical_x_right: float = 1
233+
scene_critical_y_up: float = 1
234+
scene_critical_y_down: float = 0
210235

211236
for m in mobjects:
212237
if (m == self.frame) or (
@@ -216,11 +241,12 @@ def auto_zoom(
216241
continue
217242

218243
# initialize scene critical points with first mobjects critical points
219-
if scene_critical_x_left is None:
244+
if not bounding_box_located:
220245
scene_critical_x_left = m.get_critical_point(LEFT)[0]
221246
scene_critical_x_right = m.get_critical_point(RIGHT)[0]
222247
scene_critical_y_up = m.get_critical_point(UP)[1]
223248
scene_critical_y_down = m.get_critical_point(DOWN)[1]
249+
bounding_box_located = True
224250

225251
else:
226252
if m.get_critical_point(LEFT)[0] < scene_critical_x_left:
@@ -235,17 +261,14 @@ def auto_zoom(
235261
if m.get_critical_point(DOWN)[1] < scene_critical_y_down:
236262
scene_critical_y_down = m.get_critical_point(DOWN)[1]
237263

238-
# calculate center x and y
239-
x = (scene_critical_x_left + scene_critical_x_right) / 2
240-
y = (scene_critical_y_up + scene_critical_y_down) / 2
241-
242-
# calculate proposed width and height of zoomed scene
243-
new_width = abs(scene_critical_x_left - scene_critical_x_right)
244-
new_height = abs(scene_critical_y_up - scene_critical_y_down)
264+
if not bounding_box_located:
265+
raise Exception(
266+
"Could not determine bounding box of the mobjects given to 'auto_zoom'."
267+
)
245268

246-
m_target = self.frame.animate if animate else self.frame
247-
# zoom to fit all mobjects along the side that has the largest size
248-
if new_width / self.frame.width > new_height / self.frame.height:
249-
return m_target.set_x(x).set_y(y).set(width=new_width + margin)
250-
else:
251-
return m_target.set_x(x).set_y(y).set(height=new_height + margin)
269+
return (
270+
scene_critical_x_left,
271+
scene_critical_x_right,
272+
scene_critical_y_up,
273+
scene_critical_y_down,
274+
)

mypy.ini

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,6 @@ ignore_errors = True
7575
[mypy-manim.camera.mapping_camera]
7676
ignore_errors = True
7777

78-
[mypy-manim.camera.moving_camera]
79-
ignore_errors = True
80-
8178
[mypy-manim.mobject.graphing.coordinate_systems]
8279
ignore_errors = True
8380

0 commit comments

Comments
 (0)