manim 0.17.0__py3-none-any.whl → 0.19.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. manim/__init__.py +11 -6
  2. manim/__main__.py +62 -19
  3. manim/_config/__init__.py +10 -9
  4. manim/_config/cli_colors.py +26 -9
  5. manim/_config/default.cfg +1 -3
  6. manim/_config/logger_utils.py +23 -13
  7. manim/_config/utils.py +662 -468
  8. manim/animation/animation.py +164 -18
  9. manim/animation/changing.py +34 -23
  10. manim/animation/composition.py +265 -67
  11. manim/animation/creation.py +208 -26
  12. manim/animation/fading.py +16 -18
  13. manim/animation/growing.py +35 -15
  14. manim/animation/indication.py +150 -76
  15. manim/animation/movement.py +56 -22
  16. manim/animation/numbers.py +64 -6
  17. manim/animation/rotation.py +78 -7
  18. manim/animation/specialized.py +6 -7
  19. manim/animation/speedmodifier.py +13 -10
  20. manim/animation/transform.py +14 -11
  21. manim/animation/transform_matching_parts.py +3 -4
  22. manim/animation/updaters/mobject_update_utils.py +152 -30
  23. manim/animation/updaters/update.py +10 -7
  24. manim/camera/camera.py +182 -118
  25. manim/camera/mapping_camera.py +34 -3
  26. manim/camera/moving_camera.py +95 -74
  27. manim/camera/multi_camera.py +23 -15
  28. manim/camera/three_d_camera.py +70 -52
  29. manim/cli/__init__.py +17 -0
  30. manim/cli/cfg/group.py +76 -44
  31. manim/cli/checkhealth/checks.py +192 -0
  32. manim/cli/checkhealth/commands.py +90 -0
  33. manim/cli/default_group.py +158 -25
  34. manim/cli/init/commands.py +33 -25
  35. manim/cli/plugins/commands.py +16 -3
  36. manim/cli/render/commands.py +72 -60
  37. manim/cli/render/ease_of_access_options.py +4 -3
  38. manim/cli/render/global_options.py +59 -17
  39. manim/cli/render/output_options.py +6 -5
  40. manim/cli/render/render_options.py +98 -33
  41. manim/constants.py +109 -59
  42. manim/data_structures.py +31 -0
  43. manim/mobject/frame.py +8 -5
  44. manim/mobject/geometry/__init__.py +1 -0
  45. manim/mobject/geometry/arc.py +277 -135
  46. manim/mobject/geometry/boolean_ops.py +32 -31
  47. manim/mobject/geometry/labeled.py +376 -0
  48. manim/mobject/geometry/line.py +192 -87
  49. manim/mobject/geometry/polygram.py +224 -58
  50. manim/mobject/geometry/shape_matchers.py +61 -25
  51. manim/mobject/geometry/tips.py +122 -48
  52. manim/mobject/graph.py +1027 -419
  53. manim/mobject/graphing/coordinate_systems.py +533 -278
  54. manim/mobject/graphing/functions.py +53 -32
  55. manim/mobject/graphing/number_line.py +123 -65
  56. manim/mobject/graphing/probability.py +88 -62
  57. manim/mobject/graphing/scale.py +33 -19
  58. manim/mobject/logo.py +118 -28
  59. manim/mobject/matrix.py +87 -83
  60. manim/mobject/mobject.py +912 -442
  61. manim/mobject/opengl/dot_cloud.py +16 -5
  62. manim/mobject/opengl/opengl_compatibility.py +4 -2
  63. manim/mobject/opengl/opengl_geometry.py +254 -153
  64. manim/mobject/opengl/opengl_image_mobject.py +3 -1
  65. manim/mobject/opengl/opengl_mobject.py +779 -482
  66. manim/mobject/opengl/opengl_point_cloud_mobject.py +41 -14
  67. manim/mobject/opengl/opengl_surface.py +14 -92
  68. manim/mobject/opengl/opengl_three_dimensions.py +12 -8
  69. manim/mobject/opengl/opengl_vectorized_mobject.py +98 -100
  70. manim/mobject/svg/brace.py +173 -41
  71. manim/mobject/svg/svg_mobject.py +139 -53
  72. manim/mobject/table.py +61 -68
  73. manim/mobject/text/code_mobject.py +193 -539
  74. manim/mobject/text/numbers.py +81 -34
  75. manim/mobject/text/tex_mobject.py +130 -78
  76. manim/mobject/text/text_mobject.py +288 -164
  77. manim/mobject/three_d/polyhedra.py +111 -13
  78. manim/mobject/three_d/three_d_utils.py +17 -8
  79. manim/mobject/three_d/three_dimensions.py +239 -106
  80. manim/mobject/types/image_mobject.py +50 -30
  81. manim/mobject/types/point_cloud_mobject.py +120 -75
  82. manim/mobject/types/vectorized_mobject.py +841 -408
  83. manim/mobject/value_tracker.py +105 -38
  84. manim/mobject/vector_field.py +50 -31
  85. manim/opengl/__init__.py +3 -3
  86. manim/plugins/__init__.py +14 -1
  87. manim/plugins/plugins_flags.py +10 -14
  88. manim/renderer/cairo_renderer.py +65 -50
  89. manim/renderer/opengl_renderer.py +89 -69
  90. manim/renderer/opengl_renderer_window.py +39 -18
  91. manim/renderer/shader.py +123 -87
  92. manim/renderer/shader_wrapper.py +44 -28
  93. manim/renderer/vectorized_mobject_rendering.py +38 -10
  94. manim/scene/moving_camera_scene.py +32 -3
  95. manim/scene/scene.py +507 -242
  96. manim/scene/scene_file_writer.py +371 -220
  97. manim/scene/section.py +20 -16
  98. manim/scene/three_d_scene.py +14 -22
  99. manim/scene/vector_space_scene.py +223 -129
  100. manim/scene/zoomed_scene.py +46 -41
  101. manim/typing.py +990 -0
  102. manim/utils/bezier.py +1823 -371
  103. manim/utils/caching.py +12 -5
  104. manim/utils/color/AS2700.py +236 -0
  105. manim/utils/color/BS381.py +318 -0
  106. manim/utils/color/DVIPSNAMES.py +96 -0
  107. manim/utils/color/SVGNAMES.py +179 -0
  108. manim/utils/color/X11.py +533 -0
  109. manim/utils/color/XKCD.py +952 -0
  110. manim/utils/color/__init__.py +61 -0
  111. manim/utils/color/core.py +1667 -0
  112. manim/utils/color/manim_colors.py +218 -0
  113. manim/utils/commands.py +48 -20
  114. manim/utils/config_ops.py +39 -19
  115. manim/utils/debug.py +8 -7
  116. manim/utils/deprecation.py +86 -39
  117. manim/utils/docbuild/__init__.py +17 -0
  118. manim/utils/docbuild/autoaliasattr_directive.py +236 -0
  119. manim/utils/docbuild/autocolor_directive.py +99 -0
  120. manim/utils/docbuild/manim_directive.py +94 -41
  121. manim/utils/docbuild/module_parsing.py +245 -0
  122. manim/utils/exceptions.py +6 -0
  123. manim/utils/family.py +5 -3
  124. manim/utils/family_ops.py +17 -4
  125. manim/utils/file_ops.py +27 -17
  126. manim/utils/hashing.py +55 -45
  127. manim/utils/images.py +13 -7
  128. manim/utils/ipython_magic.py +13 -7
  129. manim/utils/iterables.py +163 -120
  130. manim/utils/module_ops.py +66 -24
  131. manim/utils/opengl.py +77 -24
  132. manim/utils/parameter_parsing.py +32 -0
  133. manim/utils/paths.py +30 -33
  134. manim/utils/polylabel.py +235 -0
  135. manim/utils/qhull.py +218 -0
  136. manim/utils/rate_functions.py +98 -32
  137. manim/utils/simple_functions.py +25 -33
  138. manim/utils/sounds.py +7 -1
  139. manim/utils/space_ops.py +188 -115
  140. manim/utils/testing/__init__.py +17 -0
  141. manim/utils/testing/_frames_testers.py +13 -8
  142. manim/utils/testing/_show_diff.py +5 -3
  143. manim/utils/testing/_test_class_makers.py +34 -18
  144. manim/utils/testing/frames_comparison.py +37 -19
  145. manim/utils/tex.py +130 -198
  146. manim/utils/tex_file_writing.py +77 -47
  147. manim/utils/tex_templates.py +2 -1
  148. manim/utils/unit.py +6 -5
  149. {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/METADATA +64 -65
  150. manim-0.19.1.dist-info/RECORD +220 -0
  151. {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/WHEEL +1 -1
  152. manim-0.19.1.dist-info/entry_points.txt +3 -0
  153. {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE.community +1 -1
  154. manim/cli/new/group.py +0 -189
  155. manim/communitycolors.py +0 -9
  156. manim/gui/__init__.py +0 -0
  157. manim/gui/gui.py +0 -82
  158. manim/plugins/import_plugins.py +0 -43
  159. manim/utils/color.py +0 -552
  160. manim-0.17.0.dist-info/RECORD +0 -206
  161. manim-0.17.0.dist-info/entry_points.txt +0 -4
  162. /manim/cli/{new → checkhealth}/__init__.py +0 -0
  163. {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE +0 -0
manim/camera/camera.py CHANGED
@@ -1,6 +1,5 @@
1
1
  """A camera converts the mobjects contained in a Scene into an array of pixels."""
2
2
 
3
-
4
3
  from __future__ import annotations
5
4
 
6
5
  __all__ = ["Camera", "BackgroundColoredVMobjectDisplayer"]
@@ -9,27 +8,41 @@ import copy
9
8
  import itertools as it
10
9
  import operator as op
11
10
  import pathlib
12
- import time
11
+ from collections.abc import Callable, Iterable
13
12
  from functools import reduce
14
- from typing import Any, Callable, Iterable
13
+ from typing import TYPE_CHECKING, Any
15
14
 
16
15
  import cairo
17
16
  import numpy as np
17
+ import numpy.typing as npt
18
18
  from PIL import Image
19
19
  from scipy.spatial.distance import pdist
20
+ from typing_extensions import Self
21
+
22
+ from manim.typing import (
23
+ FloatRGBA_Array,
24
+ FloatRGBALike_Array,
25
+ ManimInt,
26
+ PixelArray,
27
+ Point3D,
28
+ Point3D_Array,
29
+ )
20
30
 
21
31
  from .. import config, logger
22
32
  from ..constants import *
23
33
  from ..mobject.mobject import Mobject
24
- from ..mobject.types.image_mobject import AbstractImageMobject
25
34
  from ..mobject.types.point_cloud_mobject import PMobject
26
35
  from ..mobject.types.vectorized_mobject import VMobject
27
- from ..utils.color import color_to_int_rgba
36
+ from ..utils.color import ManimColor, ParsableManimColor, color_to_int_rgba
28
37
  from ..utils.family import extract_mobject_family_members
29
38
  from ..utils.images import get_full_raster_image_path
30
39
  from ..utils.iterables import list_difference_update
31
40
  from ..utils.space_ops import angle_of_vector
32
41
 
42
+ if TYPE_CHECKING:
43
+ from ..mobject.types.image_mobject import AbstractImageMobject
44
+
45
+
33
46
  LINE_JOIN_MAP = {
34
47
  LineJointType.AUTO: None, # TODO: this could be improved
35
48
  LineJointType.ROUND: cairo.LineJoin.ROUND,
@@ -38,6 +51,14 @@ LINE_JOIN_MAP = {
38
51
  }
39
52
 
40
53
 
54
+ CAP_STYLE_MAP = {
55
+ CapStyleType.AUTO: None, # TODO: this could be improved
56
+ CapStyleType.ROUND: cairo.LineCap.ROUND,
57
+ CapStyleType.BUTT: cairo.LineCap.BUTT,
58
+ CapStyleType.SQUARE: cairo.LineCap.SQUARE,
59
+ }
60
+
61
+
41
62
  class Camera:
42
63
  """Base camera class.
43
64
 
@@ -63,20 +84,22 @@ class Camera:
63
84
  def __init__(
64
85
  self,
65
86
  background_image: str | None = None,
66
- frame_center: np.ndarray = ORIGIN,
87
+ frame_center: Point3D = ORIGIN,
67
88
  image_mode: str = "RGBA",
68
89
  n_channels: int = 4,
69
90
  pixel_array_dtype: str = "uint8",
70
91
  cairo_line_width_multiple: float = 0.01,
71
92
  use_z_index: bool = True,
72
- background: np.ndarray | None = None,
93
+ background: PixelArray | None = None,
73
94
  pixel_height: int | None = None,
74
95
  pixel_width: int | None = None,
75
96
  frame_height: float | None = None,
76
97
  frame_width: float | None = None,
77
98
  frame_rate: float | None = None,
78
- **kwargs,
79
- ):
99
+ background_color: ParsableManimColor | None = None,
100
+ background_opacity: float | None = None,
101
+ **kwargs: Any,
102
+ ) -> None:
80
103
  self.background_image = background_image
81
104
  self.frame_center = frame_center
82
105
  self.image_mode = image_mode
@@ -85,6 +108,9 @@ class Camera:
85
108
  self.cairo_line_width_multiple = cairo_line_width_multiple
86
109
  self.use_z_index = use_z_index
87
110
  self.background = background
111
+ self.background_colored_vmobject_displayer: (
112
+ BackgroundColoredVMobjectDisplayer | None
113
+ ) = None
88
114
 
89
115
  if pixel_height is None:
90
116
  pixel_height = config["pixel_height"]
@@ -106,16 +132,23 @@ class Camera:
106
132
  frame_rate = config["frame_rate"]
107
133
  self.frame_rate = frame_rate
108
134
 
109
- # TODO: change this to not use kwargs.get
110
- for attr in ["background_color", "background_opacity"]:
111
- setattr(self, f"_{attr}", kwargs.get(attr, config[attr]))
135
+ if background_color is None:
136
+ self._background_color: ManimColor = ManimColor.parse(
137
+ config["background_color"]
138
+ )
139
+ else:
140
+ self._background_color = ManimColor.parse(background_color)
141
+ if background_opacity is None:
142
+ self._background_opacity: float = config["background_opacity"]
143
+ else:
144
+ self._background_opacity = background_opacity
112
145
 
113
146
  # This one is in the same boat as the above, but it doesn't have the
114
147
  # same name as the corresponding key so it has to be handled on its own
115
148
  self.max_allowable_norm = config["frame_width"]
116
149
 
117
150
  self.rgb_max_val = np.iinfo(self.pixel_array_dtype).max
118
- self.pixel_array_to_cairo_context = {}
151
+ self.pixel_array_to_cairo_context: dict[int, cairo.Context] = {}
119
152
 
120
153
  # Contains the correct method to process a list of Mobjects of the
121
154
  # corresponding class. If a Mobject is not an instance of a class in
@@ -126,7 +159,7 @@ class Camera:
126
159
  self.resize_frame_shape()
127
160
  self.reset()
128
161
 
129
- def __deepcopy__(self, memo):
162
+ def __deepcopy__(self, memo: Any) -> Camera:
130
163
  # This is to address a strange bug where deepcopying
131
164
  # will result in a segfault, which is somehow related
132
165
  # to the aggdraw library
@@ -134,24 +167,26 @@ class Camera:
134
167
  return copy.copy(self)
135
168
 
136
169
  @property
137
- def background_color(self):
170
+ def background_color(self) -> ManimColor:
138
171
  return self._background_color
139
172
 
140
173
  @background_color.setter
141
- def background_color(self, color):
174
+ def background_color(self, color: ManimColor) -> None:
142
175
  self._background_color = color
143
176
  self.init_background()
144
177
 
145
178
  @property
146
- def background_opacity(self):
179
+ def background_opacity(self) -> float:
147
180
  return self._background_opacity
148
181
 
149
182
  @background_opacity.setter
150
- def background_opacity(self, alpha):
183
+ def background_opacity(self, alpha: float) -> None:
151
184
  self._background_opacity = alpha
152
185
  self.init_background()
153
186
 
154
- def type_or_raise(self, mobject: Mobject):
187
+ def type_or_raise(
188
+ self, mobject: Mobject
189
+ ) -> type[VMobject] | type[PMobject] | type[AbstractImageMobject] | type[Mobject]:
155
190
  """Return the type of mobject, if it is a type that can be rendered.
156
191
 
157
192
  If `mobject` is an instance of a class that inherits from a class that
@@ -178,10 +213,14 @@ class Camera:
178
213
  :exc:`TypeError`
179
214
  When mobject is not an instance of a class that can be rendered.
180
215
  """
181
- self.display_funcs = {
182
- VMobject: self.display_multiple_vectorized_mobjects,
183
- PMobject: self.display_multiple_point_cloud_mobjects,
184
- AbstractImageMobject: self.display_multiple_image_mobjects,
216
+ from ..mobject.types.image_mobject import AbstractImageMobject
217
+
218
+ self.display_funcs: dict[
219
+ type[Mobject], Callable[[list[Mobject], PixelArray], Any]
220
+ ] = {
221
+ VMobject: self.display_multiple_vectorized_mobjects, # type: ignore[dict-item]
222
+ PMobject: self.display_multiple_point_cloud_mobjects, # type: ignore[dict-item]
223
+ AbstractImageMobject: self.display_multiple_image_mobjects, # type: ignore[dict-item]
185
224
  Mobject: lambda batch, pa: batch, # Do nothing
186
225
  }
187
226
  # We have to check each type in turn because we are dealing with
@@ -192,7 +231,7 @@ class Camera:
192
231
  return _type
193
232
  raise TypeError(f"Displaying an object of class {_type} is not supported")
194
233
 
195
- def reset_pixel_shape(self, new_height: float, new_width: float):
234
+ def reset_pixel_shape(self, new_height: float, new_width: float) -> None:
196
235
  """This method resets the height and width
197
236
  of a single pixel to the passed new_height and new_width.
198
237
 
@@ -209,7 +248,7 @@ class Camera:
209
248
  self.resize_frame_shape()
210
249
  self.reset()
211
250
 
212
- def resize_frame_shape(self, fixed_dimension: int = 0):
251
+ def resize_frame_shape(self, fixed_dimension: int = 0) -> None:
213
252
  """
214
253
  Changes frame_shape to match the aspect ratio
215
254
  of the pixels, where fixed_dimension determines
@@ -234,7 +273,7 @@ class Camera:
234
273
  self.frame_height = frame_height
235
274
  self.frame_width = frame_width
236
275
 
237
- def init_background(self):
276
+ def init_background(self) -> None:
238
277
  """Initialize the background.
239
278
  If self.background_image is the path of an image
240
279
  the image is set as background; else, the default
@@ -260,7 +299,9 @@ class Camera:
260
299
  )
261
300
  self.background[:, :] = background_rgba
262
301
 
263
- def get_image(self, pixel_array: np.ndarray | list | tuple | None = None):
302
+ def get_image(
303
+ self, pixel_array: PixelArray | list | tuple | None = None
304
+ ) -> Image.Image:
264
305
  """Returns an image from the passed
265
306
  pixel array, or from the current frame
266
307
  if the passed pixel array is none.
@@ -272,7 +313,7 @@ class Camera:
272
313
 
273
314
  Returns
274
315
  -------
275
- PIL.Image
316
+ PIL.Image.Image
276
317
  The PIL image of the array.
277
318
  """
278
319
  if pixel_array is None:
@@ -280,8 +321,8 @@ class Camera:
280
321
  return Image.fromarray(pixel_array, mode=self.image_mode)
281
322
 
282
323
  def convert_pixel_array(
283
- self, pixel_array: np.ndarray | list | tuple, convert_from_floats: bool = False
284
- ):
324
+ self, pixel_array: PixelArray | list | tuple, convert_from_floats: bool = False
325
+ ) -> PixelArray:
285
326
  """Converts a pixel array from values that have floats in then
286
327
  to proper RGB values.
287
328
 
@@ -307,8 +348,8 @@ class Camera:
307
348
  return retval
308
349
 
309
350
  def set_pixel_array(
310
- self, pixel_array: np.ndarray | list | tuple, convert_from_floats: bool = False
311
- ):
351
+ self, pixel_array: PixelArray | list | tuple, convert_from_floats: bool = False
352
+ ) -> None:
312
353
  """Sets the pixel array of the camera to the passed pixel array.
313
354
 
314
355
  Parameters
@@ -318,19 +359,21 @@ class Camera:
318
359
  convert_from_floats
319
360
  Whether or not to convert float values to proper RGB values, by default False
320
361
  """
321
- converted_array = self.convert_pixel_array(pixel_array, convert_from_floats)
362
+ converted_array: PixelArray = self.convert_pixel_array(
363
+ pixel_array, convert_from_floats
364
+ )
322
365
  if not (
323
366
  hasattr(self, "pixel_array")
324
367
  and self.pixel_array.shape == converted_array.shape
325
368
  ):
326
- self.pixel_array = converted_array
369
+ self.pixel_array: PixelArray = converted_array
327
370
  else:
328
371
  # Set in place
329
372
  self.pixel_array[:, :, :] = converted_array[:, :, :]
330
373
 
331
374
  def set_background(
332
- self, pixel_array: np.ndarray | list | tuple, convert_from_floats: bool = False
333
- ):
375
+ self, pixel_array: PixelArray | list | tuple, convert_from_floats: bool = False
376
+ ) -> None:
334
377
  """Sets the background to the passed pixel_array after converting
335
378
  to valid RGB values.
336
379
 
@@ -346,7 +389,7 @@ class Camera:
346
389
  # TODO, this should live in utils, not as a method of Camera
347
390
  def make_background_from_func(
348
391
  self, coords_to_colors_func: Callable[[np.ndarray], np.ndarray]
349
- ):
392
+ ) -> PixelArray:
350
393
  """
351
394
  Makes a pixel array for the background by using coords_to_colors_func to determine each pixel's color. Each input
352
395
  pixel's color. Each input to coords_to_colors_func is an (x, y) pair in space (in ordinary space coordinates; not
@@ -363,7 +406,6 @@ class Camera:
363
406
  np.array
364
407
  The pixel array which can then be passed to set_background.
365
408
  """
366
-
367
409
  logger.info("Starting set_background")
368
410
  coords = self.get_coords_of_all_pixels()
369
411
  new_background = np.apply_along_axis(coords_to_colors_func, 2, coords)
@@ -373,7 +415,7 @@ class Camera:
373
415
 
374
416
  def set_background_from_func(
375
417
  self, coords_to_colors_func: Callable[[np.ndarray], np.ndarray]
376
- ):
418
+ ) -> None:
377
419
  """
378
420
  Sets the background to a pixel array using coords_to_colors_func to determine each pixel's color. Each input
379
421
  pixel's color. Each input to coords_to_colors_func is an (x, y) pair in space (in ordinary space coordinates; not
@@ -387,7 +429,7 @@ class Camera:
387
429
  """
388
430
  self.set_background(self.make_background_from_func(coords_to_colors_func))
389
431
 
390
- def reset(self):
432
+ def reset(self) -> Self:
391
433
  """Resets the camera's pixel array
392
434
  to that of the background
393
435
 
@@ -399,7 +441,7 @@ class Camera:
399
441
  self.set_pixel_array(self.background)
400
442
  return self
401
443
 
402
- def set_frame_to_background(self, background):
444
+ def set_frame_to_background(self, background: PixelArray) -> None:
403
445
  self.set_pixel_array(background)
404
446
 
405
447
  ####
@@ -409,7 +451,7 @@ class Camera:
409
451
  mobjects: Iterable[Mobject],
410
452
  include_submobjects: bool = True,
411
453
  excluded_mobjects: list | None = None,
412
- ):
454
+ ) -> list[Mobject]:
413
455
  """Used to get the list of mobjects to display
414
456
  with the camera.
415
457
 
@@ -441,7 +483,7 @@ class Camera:
441
483
  mobjects = list_difference_update(mobjects, all_excluded)
442
484
  return list(mobjects)
443
485
 
444
- def is_in_frame(self, mobject: Mobject):
486
+ def is_in_frame(self, mobject: Mobject) -> bool:
445
487
  """Checks whether the passed mobject is in
446
488
  frame or not.
447
489
 
@@ -468,7 +510,7 @@ class Camera:
468
510
  ],
469
511
  )
470
512
 
471
- def capture_mobject(self, mobject: Mobject, **kwargs: Any):
513
+ def capture_mobject(self, mobject: Mobject, **kwargs: Any) -> None:
472
514
  """Capture mobjects by storing it in :attr:`pixel_array`.
473
515
 
474
516
  This is a single-mobject version of :meth:`capture_mobjects`.
@@ -484,7 +526,7 @@ class Camera:
484
526
  """
485
527
  return self.capture_mobjects([mobject], **kwargs)
486
528
 
487
- def capture_mobjects(self, mobjects: Iterable[Mobject], **kwargs):
529
+ def capture_mobjects(self, mobjects: Iterable[Mobject], **kwargs: Any) -> None:
488
530
  """Capture mobjects by printing them on :attr:`pixel_array`.
489
531
 
490
532
  This is the essential function that converts the contents of a Scene
@@ -519,7 +561,7 @@ class Camera:
519
561
  # NOTE: None of the methods below have been mentioned outside of their definitions. Their DocStrings are not as
520
562
  # detailed as possible.
521
563
 
522
- def get_cached_cairo_context(self, pixel_array: np.ndarray):
564
+ def get_cached_cairo_context(self, pixel_array: PixelArray) -> cairo.Context | None:
523
565
  """Returns the cached cairo context of the passed
524
566
  pixel array if it exists, and None if it doesn't.
525
567
 
@@ -535,7 +577,7 @@ class Camera:
535
577
  """
536
578
  return self.pixel_array_to_cairo_context.get(id(pixel_array), None)
537
579
 
538
- def cache_cairo_context(self, pixel_array: np.ndarray, ctx: cairo.Context):
580
+ def cache_cairo_context(self, pixel_array: PixelArray, ctx: cairo.Context) -> None:
539
581
  """Caches the passed Pixel array into a Cairo Context
540
582
 
541
583
  Parameters
@@ -547,7 +589,7 @@ class Camera:
547
589
  """
548
590
  self.pixel_array_to_cairo_context[id(pixel_array)] = ctx
549
591
 
550
- def get_cairo_context(self, pixel_array: np.ndarray):
592
+ def get_cairo_context(self, pixel_array: PixelArray) -> cairo.Context:
551
593
  """Returns the cairo context for a pixel array after
552
594
  caching it to self.pixel_array_to_cairo_context
553
595
  If that array has already been cached, it returns the
@@ -572,7 +614,7 @@ class Camera:
572
614
  fh = self.frame_height
573
615
  fc = self.frame_center
574
616
  surface = cairo.ImageSurface.create_for_data(
575
- pixel_array,
617
+ pixel_array.data,
576
618
  cairo.FORMAT_ARGB32,
577
619
  pw,
578
620
  ph,
@@ -593,8 +635,8 @@ class Camera:
593
635
  return ctx
594
636
 
595
637
  def display_multiple_vectorized_mobjects(
596
- self, vmobjects: list, pixel_array: np.ndarray
597
- ):
638
+ self, vmobjects: list[VMobject], pixel_array: PixelArray
639
+ ) -> None:
598
640
  """Displays multiple VMobjects in the pixel_array
599
641
 
600
642
  Parameters
@@ -617,8 +659,8 @@ class Camera:
617
659
  )
618
660
 
619
661
  def display_multiple_non_background_colored_vmobjects(
620
- self, vmobjects: list, pixel_array: np.ndarray
621
- ):
662
+ self, vmobjects: Iterable[VMobject], pixel_array: PixelArray
663
+ ) -> None:
622
664
  """Displays multiple VMobjects in the cairo context, as long as they don't have
623
665
  background colors.
624
666
 
@@ -633,7 +675,7 @@ class Camera:
633
675
  for vmobject in vmobjects:
634
676
  self.display_vectorized(vmobject, ctx)
635
677
 
636
- def display_vectorized(self, vmobject: VMobject, ctx: cairo.Context):
678
+ def display_vectorized(self, vmobject: VMobject, ctx: cairo.Context) -> Self:
637
679
  """Displays a VMobject in the cairo context
638
680
 
639
681
  Parameters
@@ -654,7 +696,7 @@ class Camera:
654
696
  self.apply_stroke(ctx, vmobject)
655
697
  return self
656
698
 
657
- def set_cairo_context_path(self, ctx: cairo.Context, vmobject: VMobject):
699
+ def set_cairo_context_path(self, ctx: cairo.Context, vmobject: VMobject) -> Self:
658
700
  """Sets a path for the cairo context with the vmobject passed
659
701
 
660
702
  Parameters
@@ -673,7 +715,7 @@ class Camera:
673
715
  # TODO, shouldn't this be handled in transform_points_pre_display?
674
716
  # points = points - self.get_frame_center()
675
717
  if len(points) == 0:
676
- return
718
+ return self
677
719
 
678
720
  ctx.new_path()
679
721
  subpaths = vmobject.gen_subpaths_from_points_2d(points)
@@ -689,8 +731,8 @@ class Camera:
689
731
  return self
690
732
 
691
733
  def set_cairo_context_color(
692
- self, ctx: cairo.Context, rgbas: np.ndarray, vmobject: VMobject
693
- ):
734
+ self, ctx: cairo.Context, rgbas: FloatRGBALike_Array, vmobject: VMobject
735
+ ) -> Self:
694
736
  """Sets the color of the cairo context
695
737
 
696
738
  Parameters
@@ -722,7 +764,7 @@ class Camera:
722
764
  ctx.set_source(pat)
723
765
  return self
724
766
 
725
- def apply_fill(self, ctx: cairo.Context, vmobject: VMobject):
767
+ def apply_fill(self, ctx: cairo.Context, vmobject: VMobject) -> Self:
726
768
  """Fills the cairo context
727
769
 
728
770
  Parameters
@@ -743,7 +785,7 @@ class Camera:
743
785
 
744
786
  def apply_stroke(
745
787
  self, ctx: cairo.Context, vmobject: VMobject, background: bool = False
746
- ):
788
+ ) -> Self:
747
789
  """Applies a stroke to the VMobject in the cairo context.
748
790
 
749
791
  Parameters
@@ -772,15 +814,19 @@ class Camera:
772
814
  ctx.set_line_width(
773
815
  width
774
816
  * self.cairo_line_width_multiple
775
- # This ensures lines have constant width as you zoom in on them.
776
817
  * (self.frame_width / self.frame_width),
818
+ # This ensures lines have constant width as you zoom in on them.
777
819
  )
778
820
  if vmobject.joint_type != LineJointType.AUTO:
779
821
  ctx.set_line_join(LINE_JOIN_MAP[vmobject.joint_type])
822
+ if vmobject.cap_style != CapStyleType.AUTO:
823
+ ctx.set_line_cap(CAP_STYLE_MAP[vmobject.cap_style])
780
824
  ctx.stroke_preserve()
781
825
  return self
782
826
 
783
- def get_stroke_rgbas(self, vmobject: VMobject, background: bool = False):
827
+ def get_stroke_rgbas(
828
+ self, vmobject: VMobject, background: bool = False
829
+ ) -> FloatRGBA_Array:
784
830
  """Gets the RGBA array for the stroke of the passed
785
831
  VMobject.
786
832
 
@@ -799,7 +845,7 @@ class Camera:
799
845
  """
800
846
  return vmobject.get_stroke_rgbas(background)
801
847
 
802
- def get_fill_rgbas(self, vmobject: VMobject):
848
+ def get_fill_rgbas(self, vmobject: VMobject) -> FloatRGBA_Array:
803
849
  """Returns the RGBA array of the fill of the passed VMobject
804
850
 
805
851
  Parameters
@@ -814,25 +860,27 @@ class Camera:
814
860
  """
815
861
  return vmobject.get_fill_rgbas()
816
862
 
817
- def get_background_colored_vmobject_displayer(self):
863
+ def get_background_colored_vmobject_displayer(
864
+ self,
865
+ ) -> BackgroundColoredVMobjectDisplayer:
818
866
  """Returns the background_colored_vmobject_displayer
819
867
  if it exists or makes one and returns it if not.
820
868
 
821
869
  Returns
822
870
  -------
823
- BackGroundColoredVMobjectDisplayer
871
+ BackgroundColoredVMobjectDisplayer
824
872
  Object that displays VMobjects that have the same color
825
873
  as the background.
826
874
  """
827
- # Quite wordy to type out a bunch
828
- bcvd = "background_colored_vmobject_displayer"
829
- if not hasattr(self, bcvd):
830
- setattr(self, bcvd, BackgroundColoredVMobjectDisplayer(self))
831
- return getattr(self, bcvd)
875
+ if self.background_colored_vmobject_displayer is None:
876
+ self.background_colored_vmobject_displayer = (
877
+ BackgroundColoredVMobjectDisplayer(self)
878
+ )
879
+ return self.background_colored_vmobject_displayer
832
880
 
833
881
  def display_multiple_background_colored_vmobjects(
834
- self, cvmobjects: list, pixel_array: np.ndarray
835
- ):
882
+ self, cvmobjects: Iterable[VMobject], pixel_array: PixelArray
883
+ ) -> Self:
836
884
  """Displays multiple vmobjects that have the same color as the background.
837
885
 
838
886
  Parameters
@@ -858,8 +906,8 @@ class Camera:
858
906
  # As a result, the other methods do not have as detailed docstrings as would be preferred.
859
907
 
860
908
  def display_multiple_point_cloud_mobjects(
861
- self, pmobjects: list, pixel_array: np.ndarray
862
- ):
909
+ self, pmobjects: Iterable[PMobject], pixel_array: PixelArray
910
+ ) -> None:
863
911
  """Displays multiple PMobjects by modifying the passed pixel array.
864
912
 
865
913
  Parameters
@@ -881,13 +929,15 @@ class Camera:
881
929
  def display_point_cloud(
882
930
  self,
883
931
  pmobject: PMobject,
884
- points: list,
885
- rgbas: np.ndarray,
932
+ points: Point3D_Array,
933
+ rgbas: FloatRGBA_Array,
886
934
  thickness: float,
887
- pixel_array: np.ndarray,
888
- ):
889
- """Displays a PMobject by modifying the Pixel array suitably..
935
+ pixel_array: PixelArray,
936
+ ) -> None:
937
+ """Displays a PMobject by modifying the pixel array suitably.
938
+
890
939
  TODO: Write a description for the rgbas argument.
940
+
891
941
  Parameters
892
942
  ----------
893
943
  pmobject
@@ -930,8 +980,10 @@ class Camera:
930
980
  pixel_array[:, :] = new_pa.reshape((ph, pw, rgba_len))
931
981
 
932
982
  def display_multiple_image_mobjects(
933
- self, image_mobjects: list, pixel_array: np.ndarray
934
- ):
983
+ self,
984
+ image_mobjects: Iterable[AbstractImageMobject],
985
+ pixel_array: PixelArray,
986
+ ) -> None:
935
987
  """Displays multiple image mobjects by modifying the passed pixel_array.
936
988
 
937
989
  Parameters
@@ -946,7 +998,7 @@ class Camera:
946
998
 
947
999
  def display_image_mobject(
948
1000
  self, image_mobject: AbstractImageMobject, pixel_array: np.ndarray
949
- ):
1001
+ ) -> None:
950
1002
  """Displays an ImageMobject by changing the pixel_array suitably.
951
1003
 
952
1004
  Parameters
@@ -957,7 +1009,7 @@ class Camera:
957
1009
  The Pixel array to put the imagemobject in.
958
1010
  """
959
1011
  corner_coords = self.points_to_pixel_coords(image_mobject, image_mobject.points)
960
- ul_coords, ur_coords, dl_coords = corner_coords
1012
+ ul_coords, ur_coords, dl_coords, _ = corner_coords
961
1013
  right_vect = ur_coords - ul_coords
962
1014
  down_vect = dl_coords - ul_coords
963
1015
  center_coords = ul_coords + (right_vect + down_vect) / 2
@@ -965,8 +1017,8 @@ class Camera:
965
1017
  sub_image = Image.fromarray(image_mobject.get_pixel_array(), mode="RGBA")
966
1018
 
967
1019
  # Reshape
968
- pixel_width = max(int(pdist([ul_coords, ur_coords])), 1)
969
- pixel_height = max(int(pdist([ul_coords, dl_coords])), 1)
1020
+ pixel_width = max(int(pdist([ul_coords, ur_coords]).item()), 1)
1021
+ pixel_height = max(int(pdist([ul_coords, dl_coords]).item()), 1)
970
1022
  sub_image = sub_image.resize(
971
1023
  (pixel_width, pixel_height),
972
1024
  resample=image_mobject.resampling_algorithm,
@@ -1003,7 +1055,9 @@ class Camera:
1003
1055
  # Paint on top of existing pixel array
1004
1056
  self.overlay_PIL_image(pixel_array, full_image)
1005
1057
 
1006
- def overlay_rgba_array(self, pixel_array: np.ndarray, new_array: np.ndarray):
1058
+ def overlay_rgba_array(
1059
+ self, pixel_array: np.ndarray, new_array: np.ndarray
1060
+ ) -> None:
1007
1061
  """Overlays an RGBA array on top of the given Pixel array.
1008
1062
 
1009
1063
  Parameters
@@ -1015,7 +1069,7 @@ class Camera:
1015
1069
  """
1016
1070
  self.overlay_PIL_image(pixel_array, self.get_image(new_array))
1017
1071
 
1018
- def overlay_PIL_image(self, pixel_array: np.ndarray, image: Image):
1072
+ def overlay_PIL_image(self, pixel_array: np.ndarray, image: Image) -> None:
1019
1073
  """Overlays a PIL image on the passed pixel array.
1020
1074
 
1021
1075
  Parameters
@@ -1030,7 +1084,7 @@ class Camera:
1030
1084
  dtype="uint8",
1031
1085
  )
1032
1086
 
1033
- def adjust_out_of_range_points(self, points: np.ndarray):
1087
+ def adjust_out_of_range_points(self, points: np.ndarray) -> np.ndarray:
1034
1088
  """If any of the points in the passed array are out of
1035
1089
  the viable range, they are adjusted suitably.
1036
1090
 
@@ -1061,9 +1115,9 @@ class Camera:
1061
1115
 
1062
1116
  def transform_points_pre_display(
1063
1117
  self,
1064
- mobject,
1065
- points,
1066
- ): # TODO: Write more detailed docstrings for this method.
1118
+ mobject: Mobject,
1119
+ points: Point3D_Array,
1120
+ ) -> Point3D_Array: # TODO: Write more detailed docstrings for this method.
1067
1121
  # NOTE: There seems to be an unused argument `mobject`.
1068
1122
 
1069
1123
  # Subclasses (like ThreeDCamera) may want to
@@ -1076,9 +1130,9 @@ class Camera:
1076
1130
 
1077
1131
  def points_to_pixel_coords(
1078
1132
  self,
1079
- mobject,
1080
- points,
1081
- ): # TODO: Write more detailed docstrings for this method.
1133
+ mobject: Mobject,
1134
+ points: Point3D_Array,
1135
+ ) -> npt.NDArray[ManimInt]: # TODO: Write more detailed docstrings for this method.
1082
1136
  points = self.transform_points_pre_display(mobject, points)
1083
1137
  shifted_points = points - self.frame_center
1084
1138
 
@@ -1098,7 +1152,7 @@ class Camera:
1098
1152
  result[:, 1] = shifted_points[:, 1] * height_mult + height_add
1099
1153
  return result.astype("int")
1100
1154
 
1101
- def on_screen_pixels(self, pixel_coords: np.ndarray):
1155
+ def on_screen_pixels(self, pixel_coords: np.ndarray) -> PixelArray:
1102
1156
  """Returns array of pixels that are on the screen from a given
1103
1157
  array of pixel_coordinates
1104
1158
 
@@ -1122,26 +1176,29 @@ class Camera:
1122
1176
  ],
1123
1177
  )
1124
1178
 
1125
- def adjusted_thickness(self, thickness: float):
1126
- """
1179
+ def adjusted_thickness(self, thickness: float) -> float:
1180
+ """Computes the adjusted stroke width for a zoomed camera.
1127
1181
 
1128
1182
  Parameters
1129
1183
  ----------
1130
1184
  thickness
1185
+ The stroke width of a mobject.
1131
1186
 
1132
1187
  Returns
1133
1188
  -------
1134
1189
  float
1135
-
1190
+ The adjusted stroke width that reflects zooming in with
1191
+ the camera.
1136
1192
  """
1137
1193
  # TODO: This seems...unsystematic
1138
- big_sum = op.add(config["pixel_height"], config["pixel_width"])
1139
- this_sum = op.add(self.pixel_height, self.pixel_width)
1194
+ big_sum: float = op.add(config["pixel_height"], config["pixel_width"])
1195
+ this_sum: float = op.add(self.pixel_height, self.pixel_width)
1140
1196
  factor = big_sum / this_sum
1141
1197
  return 1 + (thickness - 1) * factor
1142
1198
 
1143
- def get_thickening_nudges(self, thickness: float):
1144
- """
1199
+ def get_thickening_nudges(self, thickness: float) -> PixelArray:
1200
+ """Determine a list of vectors used to nudge
1201
+ two-dimensional pixel coordinates.
1145
1202
 
1146
1203
  Parameters
1147
1204
  ----------
@@ -1156,7 +1213,9 @@ class Camera:
1156
1213
  _range = list(range(-thickness // 2 + 1, thickness // 2 + 1))
1157
1214
  return np.array(list(it.product(_range, _range)))
1158
1215
 
1159
- def thickened_coordinates(self, pixel_coords: np.ndarray, thickness: float):
1216
+ def thickened_coordinates(
1217
+ self, pixel_coords: np.ndarray, thickness: float
1218
+ ) -> PixelArray:
1160
1219
  """Returns thickened coordinates for a passed array of pixel coords and
1161
1220
  a thickness to thicken by.
1162
1221
 
@@ -1178,7 +1237,7 @@ class Camera:
1178
1237
  return pixel_coords.reshape((size // 2, 2))
1179
1238
 
1180
1239
  # TODO, reimplement using cairo matrix
1181
- def get_coords_of_all_pixels(self):
1240
+ def get_coords_of_all_pixels(self) -> PixelArray:
1182
1241
  """Returns the cartesian coordinates of each pixel.
1183
1242
 
1184
1243
  Returns
@@ -1215,28 +1274,31 @@ class Camera:
1215
1274
  # NOTE: The methods of the following class have not been mentioned outside of their definitions.
1216
1275
  # Their DocStrings are not as detailed as preferred.
1217
1276
  class BackgroundColoredVMobjectDisplayer:
1277
+ """Auxiliary class that handles displaying vectorized mobjects with
1278
+ a set background image.
1279
+
1280
+ Parameters
1281
+ ----------
1282
+ camera
1283
+ Camera object to use.
1284
+ """
1285
+
1218
1286
  def __init__(self, camera: Camera):
1219
- """
1220
- Parameters
1221
- ----------
1222
- camera
1223
- Camera object to use.
1224
- """
1225
1287
  self.camera = camera
1226
- self.file_name_to_pixel_array_map = {}
1288
+ self.file_name_to_pixel_array_map: dict[str, PixelArray] = {}
1227
1289
  self.pixel_array = np.array(camera.pixel_array)
1228
1290
  self.reset_pixel_array()
1229
1291
 
1230
- def reset_pixel_array(self):
1292
+ def reset_pixel_array(self) -> None:
1231
1293
  self.pixel_array[:, :] = 0
1232
1294
 
1233
1295
  def resize_background_array(
1234
1296
  self,
1235
- background_array: np.ndarray,
1297
+ background_array: PixelArray,
1236
1298
  new_width: float,
1237
1299
  new_height: float,
1238
1300
  mode: str = "RGBA",
1239
- ):
1301
+ ) -> PixelArray:
1240
1302
  """Resizes the pixel array representing the background.
1241
1303
 
1242
1304
  Parameters
@@ -1261,8 +1323,8 @@ class BackgroundColoredVMobjectDisplayer:
1261
1323
  return np.array(resized_image)
1262
1324
 
1263
1325
  def resize_background_array_to_match(
1264
- self, background_array: np.ndarray, pixel_array: np.ndarray
1265
- ):
1326
+ self, background_array: PixelArray, pixel_array: PixelArray
1327
+ ) -> PixelArray:
1266
1328
  """Resizes the background array to match the passed pixel array.
1267
1329
 
1268
1330
  Parameters
@@ -1281,7 +1343,9 @@ class BackgroundColoredVMobjectDisplayer:
1281
1343
  mode = "RGBA" if pixel_array.shape[2] == 4 else "RGB"
1282
1344
  return self.resize_background_array(background_array, width, height, mode)
1283
1345
 
1284
- def get_background_array(self, image: Image.Image | pathlib.Path | str):
1346
+ def get_background_array(
1347
+ self, image: Image.Image | pathlib.Path | str
1348
+ ) -> PixelArray:
1285
1349
  """Gets the background array that has the passed file_name.
1286
1350
 
1287
1351
  Parameters
@@ -1310,7 +1374,7 @@ class BackgroundColoredVMobjectDisplayer:
1310
1374
  self.file_name_to_pixel_array_map[image_key] = back_array
1311
1375
  return back_array
1312
1376
 
1313
- def display(self, *cvmobjects: VMobject):
1377
+ def display(self, *cvmobjects: VMobject) -> PixelArray | None:
1314
1378
  """Displays the colored VMobjects.
1315
1379
 
1316
1380
  Parameters