12
12
from collections .abc import Iterable
13
13
from typing import Any
14
14
15
- import numpy as np
15
+ from cairo import Context
16
+
17
+ from manim .typing import PixelArray , Point3D , Point3DLike
16
18
17
19
from .. import config
18
20
from ..camera .camera import Camera
@@ -34,12 +36,12 @@ class MovingCamera(Camera):
34
36
35
37
def __init__ (
36
38
self ,
37
- frame = None ,
39
+ frame : Mobject | None = None ,
38
40
fixed_dimension : int = 0 , # width
39
41
default_frame_stroke_color : ManimColor = WHITE ,
40
42
default_frame_stroke_width : int = 0 ,
41
43
** kwargs : Any ,
42
- ) -> None :
44
+ ):
43
45
"""Frame is a Mobject, (should almost certainly be a rectangle)
44
46
determining which region of space the camera displays
45
47
"""
@@ -57,7 +59,7 @@ def __init__(
57
59
58
60
# TODO, make these work for a rotated frame
59
61
@property
60
- def frame_height (self ):
62
+ def frame_height (self ) -> float :
61
63
"""Returns the height of the frame.
62
64
63
65
Returns
@@ -67,30 +69,8 @@ def frame_height(self):
67
69
"""
68
70
return self .frame .height
69
71
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
-
92
72
@frame_height .setter
93
- def frame_height (self , frame_height : float ):
73
+ def frame_height (self , frame_height : float ) -> None :
94
74
"""Sets the height of the frame in MUnits.
95
75
96
76
Parameters
@@ -100,8 +80,19 @@ def frame_height(self, frame_height: float):
100
80
"""
101
81
self .frame .stretch_to_fit_height (frame_height )
102
82
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
+
103
94
@frame_width .setter
104
- def frame_width (self , frame_width : float ):
95
+ def frame_width (self , frame_width : float ) -> None :
105
96
"""Sets the width of the frame in MUnits.
106
97
107
98
Parameters
@@ -111,8 +102,19 @@ def frame_width(self, frame_width: float):
111
102
"""
112
103
self .frame .stretch_to_fit_width (frame_width )
113
104
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
+
114
116
@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 :
116
118
"""Sets the centerpoint of the frame.
117
119
118
120
Parameters
@@ -129,17 +131,14 @@ def capture_mobjects(self, mobjects: Iterable[Mobject], **kwargs: Any) -> None:
129
131
# self.realign_frame_shape()
130
132
super ().capture_mobjects (mobjects , ** kwargs )
131
133
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 :
136
135
"""Since the frame can be moving around, the cairo
137
136
context used for updating should be regenerated
138
137
at each frame. So no caching.
139
138
"""
140
139
return None
141
140
142
- def cache_cairo_context (self , pixel_array , ctx ) :
141
+ def cache_cairo_context (self , pixel_array : PixelArray , ctx : Context ) -> None :
143
142
"""Since the frame can be moving around, the cairo
144
143
context used for updating should be regenerated
145
144
at each frame. So no caching.
@@ -157,23 +156,23 @@ def cache_cairo_context(self, pixel_array, ctx):
157
156
# self.frame_shape = (self.frame.height, width)
158
157
# self.resize_frame_shape(fixed_dimension=self.fixed_dimension)
159
158
160
- def get_mobjects_indicating_movement (self ):
159
+ def get_mobjects_indicating_movement (self ) -> list [ Mobject ] :
161
160
"""Returns all mobjects whose movement implies that the camera
162
161
should think of all other mobjects on the screen as moving
163
162
164
163
Returns
165
164
-------
166
- list
165
+ list[Mobject]
167
166
"""
168
167
return [self .frame ]
169
168
170
169
def auto_zoom (
171
170
self ,
172
- mobjects : list [Mobject ],
171
+ mobjects : Iterable [Mobject ],
173
172
margin : float = 0 ,
174
173
only_mobjects_in_frame : bool = False ,
175
174
animate : bool = True ,
176
- ):
175
+ ) -> Mobject :
177
176
"""Zooms on to a given array of mobjects (or a singular mobject)
178
177
and automatically resizes to frame all the mobjects.
179
178
@@ -203,10 +202,36 @@ def auto_zoom(
203
202
or ScreenRectangle with position and size updated to zoomed position.
204
203
205
204
"""
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
210
235
211
236
for m in mobjects :
212
237
if (m == self .frame ) or (
@@ -216,11 +241,12 @@ def auto_zoom(
216
241
continue
217
242
218
243
# initialize scene critical points with first mobjects critical points
219
- if scene_critical_x_left is None :
244
+ if not bounding_box_located :
220
245
scene_critical_x_left = m .get_critical_point (LEFT )[0 ]
221
246
scene_critical_x_right = m .get_critical_point (RIGHT )[0 ]
222
247
scene_critical_y_up = m .get_critical_point (UP )[1 ]
223
248
scene_critical_y_down = m .get_critical_point (DOWN )[1 ]
249
+ bounding_box_located = True
224
250
225
251
else :
226
252
if m .get_critical_point (LEFT )[0 ] < scene_critical_x_left :
@@ -235,17 +261,14 @@ def auto_zoom(
235
261
if m .get_critical_point (DOWN )[1 ] < scene_critical_y_down :
236
262
scene_critical_y_down = m .get_critical_point (DOWN )[1 ]
237
263
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
+ )
245
268
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