1212from collections .abc import Iterable
1313from typing import Any
1414
15- import numpy as np
15+ from cairo import Context
16+
17+ from manim .typing import PixelArray , Point3D , Point3DLike
1618
1719from .. import config
1820from ..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+ )
0 commit comments