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
@@ -13,21 +13,36 @@ __all__ = [
13
13
  "Square",
14
14
  "RoundedRectangle",
15
15
  "Cutout",
16
+ "ConvexHull",
16
17
  ]
17
18
 
18
- from typing import Iterable, Sequence
19
+
20
+ from math import ceil
21
+ from typing import TYPE_CHECKING, Any, Literal
19
22
 
20
23
  import numpy as np
21
- from colour import Color
22
24
 
23
25
  from manim.constants import *
24
26
  from manim.mobject.geometry.arc import ArcBetweenPoints
25
27
  from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL
26
28
  from manim.mobject.types.vectorized_mobject import VGroup, VMobject
27
- from manim.utils.color import *
29
+ from manim.utils.color import BLUE, WHITE, ParsableManimColor
28
30
  from manim.utils.iterables import adjacent_n_tuples, adjacent_pairs
31
+ from manim.utils.qhull import QuickHull
29
32
  from manim.utils.space_ops import angle_between_vectors, normalize, regular_vertices
30
33
 
34
+ if TYPE_CHECKING:
35
+ import numpy.typing as npt
36
+ from typing_extensions import Self
37
+
38
+ from manim.typing import (
39
+ Point3D,
40
+ Point3D_Array,
41
+ Point3DLike,
42
+ Point3DLike_Array,
43
+ )
44
+ from manim.utils.color import ParsableManimColor
45
+
31
46
 
32
47
  class Polygram(VMobject, metaclass=ConvertToOpenGL):
33
48
  """A generalized :class:`Polygon`, allowing for disconnected sets of edges.
@@ -64,10 +79,17 @@ class Polygram(VMobject, metaclass=ConvertToOpenGL):
64
79
  self.wait()
65
80
  """
66
81
 
67
- def __init__(self, *vertex_groups: Iterable[Sequence[float]], color=BLUE, **kwargs):
82
+ def __init__(
83
+ self,
84
+ *vertex_groups: Point3DLike_Array,
85
+ color: ParsableManimColor = BLUE,
86
+ **kwargs: Any,
87
+ ):
68
88
  super().__init__(color=color, **kwargs)
69
89
 
70
90
  for vertices in vertex_groups:
91
+ # The inferred type for *vertices is Any, but it should be
92
+ # Point3D_Array
71
93
  first_vertex, *vertices = vertices
72
94
  first_vertex = np.array(first_vertex)
73
95
 
@@ -76,7 +98,7 @@ class Polygram(VMobject, metaclass=ConvertToOpenGL):
76
98
  [*(np.array(vertex) for vertex in vertices), first_vertex],
77
99
  )
78
100
 
79
- def get_vertices(self) -> np.ndarray:
101
+ def get_vertices(self) -> Point3D_Array:
80
102
  """Gets the vertices of the :class:`Polygram`.
81
103
 
82
104
  Returns
@@ -95,56 +117,94 @@ class Polygram(VMobject, metaclass=ConvertToOpenGL):
95
117
  [-1., -1., 0.],
96
118
  [ 1., -1., 0.]])
97
119
  """
98
-
99
120
  return self.get_start_anchors()
100
121
 
101
- def get_vertex_groups(self) -> np.ndarray:
122
+ def get_vertex_groups(self) -> list[Point3D_Array]:
102
123
  """Gets the vertex groups of the :class:`Polygram`.
103
124
 
104
125
  Returns
105
126
  -------
106
- :class:`numpy.ndarray`
107
- The vertex groups of the :class:`Polygram`.
127
+ list[Point3D_Array]
128
+ The list of vertex groups of the :class:`Polygram`.
108
129
 
109
130
  Examples
110
131
  --------
111
132
  ::
112
133
 
113
- >>> poly = Polygram([ORIGIN, RIGHT, UP], [LEFT, LEFT + UP, 2 * LEFT])
114
- >>> poly.get_vertex_groups()
115
- array([[[ 0., 0., 0.],
116
- [ 1., 0., 0.],
117
- [ 0., 1., 0.]],
118
- <BLANKLINE>
119
- [[-1., 0., 0.],
120
- [-1., 1., 0.],
121
- [-2., 0., 0.]]])
134
+ >>> poly = Polygram([ORIGIN, RIGHT, UP, LEFT + UP], [LEFT, LEFT + UP, 2 * LEFT])
135
+ >>> groups = poly.get_vertex_groups()
136
+ >>> len(groups)
137
+ 2
138
+ >>> groups[0]
139
+ array([[ 0., 0., 0.],
140
+ [ 1., 0., 0.],
141
+ [ 0., 1., 0.],
142
+ [-1., 1., 0.]])
143
+ >>> groups[1]
144
+ array([[-1., 0., 0.],
145
+ [-1., 1., 0.],
146
+ [-2., 0., 0.]])
122
147
  """
123
-
124
148
  vertex_groups = []
125
149
 
150
+ # TODO: If any of the original vertex groups contained the starting vertex N
151
+ # times, then .get_vertex_groups() splits it into N vertex groups.
126
152
  group = []
127
153
  for start, end in zip(self.get_start_anchors(), self.get_end_anchors()):
128
154
  group.append(start)
129
155
 
130
156
  if self.consider_points_equals(end, group[0]):
131
- vertex_groups.append(group)
157
+ vertex_groups.append(np.array(group))
132
158
  group = []
133
159
 
134
- return np.array(vertex_groups)
160
+ return vertex_groups
135
161
 
136
- def round_corners(self, radius: float = 0.5):
162
+ def round_corners(
163
+ self,
164
+ radius: float | list[float] = 0.5,
165
+ evenly_distribute_anchors: bool = False,
166
+ components_per_rounded_corner: int = 2,
167
+ ) -> Self:
137
168
  """Rounds off the corners of the :class:`Polygram`.
138
169
 
139
170
  Parameters
140
171
  ----------
141
172
  radius
142
173
  The curvature of the corners of the :class:`Polygram`.
174
+ evenly_distribute_anchors
175
+ Break long line segments into proportionally-sized segments.
176
+ components_per_rounded_corner
177
+ The number of points used to represent the rounded corner curve.
143
178
 
144
179
 
145
180
  .. seealso::
146
181
  :class:`.~RoundedRectangle`
147
182
 
183
+ .. note::
184
+ If `radius` is supplied as a single value, then the same radius
185
+ will be applied to all corners. If `radius` is a list, then the
186
+ individual values will be applied sequentially, with the first
187
+ corner receiving `radius[0]`, the second corner receiving
188
+ `radius[1]`, etc. The radius list will be repeated as necessary.
189
+
190
+ The `components_per_rounded_corner` value is provided so that the
191
+ fidelity of the rounded corner may be fine-tuned as needed. 2 is
192
+ an appropriate value for most shapes, however a larger value may be
193
+ need if the rounded corner is particularly large. 2 is the minimum
194
+ number allowed, representing the start and end of the curve. 3 will
195
+ result in a start, middle, and end point, meaning 2 curves will be
196
+ generated.
197
+
198
+ The option to `evenly_distribute_anchors` is provided so that the
199
+ line segments (the part part of each line remaining after rounding
200
+ off the corners) can be subdivided to a density similar to that of
201
+ the average density of the rounded corners. This may be desirable
202
+ in situations in which an even distribution of curves is desired
203
+ for use in later transformation animations. Be aware, though, that
204
+ enabling this option can result in an an object containing
205
+ significantly more points than the original, especially when the
206
+ rounded corner curves are small.
207
+
148
208
  Examples
149
209
  --------
150
210
  .. manim:: PolygramRoundCorners
@@ -161,15 +221,24 @@ class Polygram(VMobject, metaclass=ConvertToOpenGL):
161
221
  shapes.arrange(RIGHT)
162
222
  self.add(shapes)
163
223
  """
164
-
165
224
  if radius == 0:
166
225
  return self
167
226
 
168
- new_points = []
227
+ new_points: list[Point3D] = []
169
228
 
170
- for vertices in self.get_vertex_groups():
229
+ for vertex_group in self.get_vertex_groups():
171
230
  arcs = []
172
- for v1, v2, v3 in adjacent_n_tuples(vertices, 3):
231
+
232
+ # Repeat the radius list as necessary in order to provide a radius
233
+ # for each vertex.
234
+ if isinstance(radius, (int, float)):
235
+ radius_list = [radius] * len(vertex_group)
236
+ else:
237
+ radius_list = radius * ceil(len(vertex_group) / len(radius))
238
+
239
+ for current_radius, (v1, v2, v3) in zip(
240
+ radius_list, adjacent_n_tuples(vertex_group, 3)
241
+ ):
173
242
  vect1 = v2 - v1
174
243
  vect2 = v3 - v2
175
244
  unit_vect1 = normalize(vect1)
@@ -177,10 +246,10 @@ class Polygram(VMobject, metaclass=ConvertToOpenGL):
177
246
 
178
247
  angle = angle_between_vectors(vect1, vect2)
179
248
  # Negative radius gives concave curves
180
- angle *= np.sign(radius)
249
+ angle *= np.sign(current_radius)
181
250
 
182
251
  # Distance between vertex and start of the arc
183
- cut_off_length = radius * np.tan(angle / 2)
252
+ cut_off_length = current_radius * np.tan(angle / 2)
184
253
 
185
254
  # Determines counterclockwise vs. clockwise
186
255
  sign = np.sign(np.cross(vect1, vect2)[2])
@@ -189,9 +258,24 @@ class Polygram(VMobject, metaclass=ConvertToOpenGL):
189
258
  v2 - unit_vect1 * cut_off_length,
190
259
  v2 + unit_vect2 * cut_off_length,
191
260
  angle=sign * angle,
261
+ num_components=components_per_rounded_corner,
192
262
  )
193
263
  arcs.append(arc)
194
264
 
265
+ if evenly_distribute_anchors:
266
+ # Determine the average length of each curve
267
+ nonzero_length_arcs = [arc for arc in arcs if len(arc.points) > 4]
268
+ if len(nonzero_length_arcs) > 0:
269
+ total_arc_length = sum(
270
+ [arc.get_arc_length() for arc in nonzero_length_arcs]
271
+ )
272
+ num_curves = (
273
+ sum([len(arc.points) for arc in nonzero_length_arcs]) / 4
274
+ )
275
+ average_arc_length = total_arc_length / num_curves
276
+ else:
277
+ average_arc_length = 1.0
278
+
195
279
  # To ensure that we loop through starting with last
196
280
  arcs = [arcs[-1], *arcs[:-1]]
197
281
  from manim.mobject.geometry.line import Line
@@ -201,14 +285,13 @@ class Polygram(VMobject, metaclass=ConvertToOpenGL):
201
285
 
202
286
  line = Line(arc1.get_end(), arc2.get_start())
203
287
 
204
- # Make sure anchors are evenly distributed
205
- len_ratio = line.get_length() / arc1.get_arc_length()
206
-
207
- line.insert_n_curves(int(arc1.get_num_curves() * len_ratio))
288
+ # Make sure anchors are evenly distributed, if necessary
289
+ if evenly_distribute_anchors:
290
+ line.insert_n_curves(ceil(line.get_length() / average_arc_length))
208
291
 
209
292
  new_points.extend(line.points)
210
293
 
211
- self.set_points(new_points)
294
+ self.set_points(np.array(new_points))
212
295
 
213
296
  return self
214
297
 
@@ -243,7 +326,7 @@ class Polygon(Polygram):
243
326
  self.add(isosceles, square_and_triangles)
244
327
  """
245
328
 
246
- def __init__(self, *vertices: Sequence[float], **kwargs):
329
+ def __init__(self, *vertices: Point3DLike, **kwargs: Any) -> None:
247
330
  super().__init__(vertices, **kwargs)
248
331
 
249
332
 
@@ -286,8 +369,8 @@ class RegularPolygram(Polygram):
286
369
  density: int = 2,
287
370
  radius: float = 1,
288
371
  start_angle: float | None = None,
289
- **kwargs,
290
- ):
372
+ **kwargs: Any,
373
+ ) -> None:
291
374
  # Regular polygrams can be expressed by the number of their vertices
292
375
  # and their density. This relation can be expressed as its Schläfli
293
376
  # symbol: {num_vertices/density}.
@@ -307,7 +390,7 @@ class RegularPolygram(Polygram):
307
390
 
308
391
  # Utility function for generating the individual
309
392
  # polygon vertices.
310
- def gen_polygon_vertices(start_angle):
393
+ def gen_polygon_vertices(start_angle: float | None) -> tuple[list[Any], float]:
311
394
  reg_vertices, start_angle = regular_vertices(
312
395
  num_vertices,
313
396
  radius=radius,
@@ -363,7 +446,7 @@ class RegularPolygon(RegularPolygram):
363
446
  self.add(poly_group)
364
447
  """
365
448
 
366
- def __init__(self, n: int = 6, **kwargs):
449
+ def __init__(self, n: int = 6, **kwargs: Any) -> None:
367
450
  super().__init__(n, density=1, **kwargs)
368
451
 
369
452
 
@@ -403,7 +486,6 @@ class Star(Polygon):
403
486
  Examples
404
487
  --------
405
488
  .. manim:: StarExample
406
- :save_as_gif:
407
489
 
408
490
  class StarExample(Scene):
409
491
  def construct(self):
@@ -434,8 +516,8 @@ class Star(Polygon):
434
516
  inner_radius: float | None = None,
435
517
  density: int = 2,
436
518
  start_angle: float | None = TAU / 4,
437
- **kwargs,
438
- ):
519
+ **kwargs: Any,
520
+ ) -> None:
439
521
  inner_angle = TAU / (2 * n)
440
522
 
441
523
  if inner_radius is None:
@@ -466,7 +548,7 @@ class Star(Polygon):
466
548
  start_angle=self.start_angle + inner_angle,
467
549
  )
468
550
 
469
- vertices = []
551
+ vertices: list[npt.NDArray] = []
470
552
  for pair in zip(outer_vertices, inner_vertices):
471
553
  vertices.extend(pair)
472
554
 
@@ -494,7 +576,7 @@ class Triangle(RegularPolygon):
494
576
  self.add(tri_group)
495
577
  """
496
578
 
497
- def __init__(self, **kwargs):
579
+ def __init__(self, **kwargs: Any) -> None:
498
580
  super().__init__(n=3, **kwargs)
499
581
 
500
582
 
@@ -529,29 +611,37 @@ class Rectangle(Polygon):
529
611
  def construct(self):
530
612
  rect1 = Rectangle(width=4.0, height=2.0, grid_xstep=1.0, grid_ystep=0.5)
531
613
  rect2 = Rectangle(width=1.0, height=4.0)
614
+ rect3 = Rectangle(width=2.0, height=2.0, grid_xstep=1.0, grid_ystep=1.0)
615
+ rect3.grid_lines.set_stroke(width=1)
532
616
 
533
- rects = Group(rect1,rect2).arrange(buff=1)
617
+ rects = Group(rect1, rect2, rect3).arrange(buff=1)
534
618
  self.add(rects)
535
619
  """
536
620
 
537
621
  def __init__(
538
622
  self,
539
- color: Color = WHITE,
623
+ color: ParsableManimColor = WHITE,
540
624
  height: float = 2.0,
541
625
  width: float = 4.0,
542
626
  grid_xstep: float | None = None,
543
627
  grid_ystep: float | None = None,
544
628
  mark_paths_closed: bool = True,
545
629
  close_new_points: bool = True,
546
- **kwargs,
630
+ **kwargs: Any,
547
631
  ):
548
632
  super().__init__(UR, UL, DL, DR, color=color, **kwargs)
549
633
  self.stretch_to_fit_width(width)
550
634
  self.stretch_to_fit_height(height)
635
+
551
636
  v = self.get_vertices()
552
- if grid_xstep is not None:
637
+ self.grid_lines = VGroup()
638
+
639
+ if grid_xstep or grid_ystep:
553
640
  from manim.mobject.geometry.line import Line
554
641
 
642
+ v = self.get_vertices()
643
+
644
+ if grid_xstep:
555
645
  grid_xstep = abs(grid_xstep)
556
646
  count = int(width / grid_xstep)
557
647
  grid = VGroup(
@@ -564,8 +654,9 @@ class Rectangle(Polygon):
564
654
  for i in range(1, count)
565
655
  )
566
656
  )
567
- self.add(grid)
568
- if grid_ystep is not None:
657
+ self.grid_lines.add(grid)
658
+
659
+ if grid_ystep:
569
660
  grid_ystep = abs(grid_ystep)
570
661
  count = int(height / grid_ystep)
571
662
  grid = VGroup(
@@ -578,7 +669,10 @@ class Rectangle(Polygon):
578
669
  for i in range(1, count)
579
670
  )
580
671
  )
581
- self.add(grid)
672
+ self.grid_lines.add(grid)
673
+
674
+ if self.grid_lines:
675
+ self.add(self.grid_lines)
582
676
 
583
677
 
584
678
  class Square(Rectangle):
@@ -604,10 +698,17 @@ class Square(Rectangle):
604
698
  self.add(square_1, square_2, square_3)
605
699
  """
606
700
 
607
- def __init__(self, side_length: float = 2.0, **kwargs):
608
- self.side_length = side_length
701
+ def __init__(self, side_length: float = 2.0, **kwargs: Any) -> None:
609
702
  super().__init__(height=side_length, width=side_length, **kwargs)
610
703
 
704
+ @property
705
+ def side_length(self) -> float:
706
+ return float(np.linalg.norm(self.get_vertices()[0] - self.get_vertices()[1]))
707
+
708
+ @side_length.setter
709
+ def side_length(self, value: float) -> None:
710
+ self.scale(value / self.side_length)
711
+
611
712
 
612
713
  class RoundedRectangle(Rectangle):
613
714
  """A rectangle with rounded corners.
@@ -633,7 +734,7 @@ class RoundedRectangle(Rectangle):
633
734
  self.add(rect_group)
634
735
  """
635
736
 
636
- def __init__(self, corner_radius: float = 0.5, **kwargs):
737
+ def __init__(self, corner_radius: float | list[float] = 0.5, **kwargs: Any):
637
738
  super().__init__(**kwargs)
638
739
  self.corner_radius = corner_radius
639
740
  self.round_corners(self.corner_radius)
@@ -674,12 +775,77 @@ class Cutout(VMobject, metaclass=ConvertToOpenGL):
674
775
  self.wait()
675
776
  """
676
777
 
677
- def __init__(self, main_shape: VMobject, *mobjects: VMobject, **kwargs):
778
+ def __init__(
779
+ self, main_shape: VMobject, *mobjects: VMobject, **kwargs: Any
780
+ ) -> None:
678
781
  super().__init__(**kwargs)
679
782
  self.append_points(main_shape.points)
680
- if main_shape.get_direction() == "CW":
681
- sub_direction = "CCW"
682
- else:
683
- sub_direction = "CW"
783
+ sub_direction: Literal["CCW", "CW"] = (
784
+ "CCW" if main_shape.get_direction() == "CW" else "CW"
785
+ )
684
786
  for mobject in mobjects:
685
787
  self.append_points(mobject.force_direction(sub_direction).points)
788
+
789
+
790
+ class ConvexHull(Polygram):
791
+ """Constructs a convex hull for a set of points in no particular order.
792
+
793
+ Parameters
794
+ ----------
795
+ points
796
+ The points to consider.
797
+ tolerance
798
+ The tolerance used by quickhull.
799
+ kwargs
800
+ Forwarded to the parent constructor.
801
+
802
+ Examples
803
+ --------
804
+ .. manim:: ConvexHullExample
805
+ :save_last_frame:
806
+ :quality: high
807
+
808
+ class ConvexHullExample(Scene):
809
+ def construct(self):
810
+ points = [
811
+ [-2.35, -2.25, 0],
812
+ [1.65, -2.25, 0],
813
+ [2.65, -0.25, 0],
814
+ [1.65, 1.75, 0],
815
+ [-0.35, 2.75, 0],
816
+ [-2.35, 0.75, 0],
817
+ [-0.35, -1.25, 0],
818
+ [0.65, -0.25, 0],
819
+ [-1.35, 0.25, 0],
820
+ [0.15, 0.75, 0]
821
+ ]
822
+ hull = ConvexHull(*points, color=BLUE)
823
+ dots = VGroup(*[Dot(point) for point in points])
824
+ self.add(hull)
825
+ self.add(dots)
826
+ """
827
+
828
+ def __init__(
829
+ self, *points: Point3DLike, tolerance: float = 1e-5, **kwargs: Any
830
+ ) -> None:
831
+ # Build Convex Hull
832
+ array = np.array(points)[:, :2]
833
+ hull = QuickHull(tolerance)
834
+ hull.build(array)
835
+
836
+ # Extract Vertices
837
+ facets = set(hull.facets) - hull.removed
838
+ facet = facets.pop()
839
+ subfacets = list(facet.subfacets)
840
+ while len(subfacets) <= len(facets):
841
+ sf = subfacets[-1]
842
+ (facet,) = hull.neighbors[sf] - {facet}
843
+ (sf,) = facet.subfacets - {sf}
844
+ subfacets.append(sf)
845
+
846
+ # Setup Vertices as Point3D
847
+ coordinates = np.vstack([sf.coordinates for sf in subfacets])
848
+ vertices = np.hstack((coordinates, np.zeros((len(coordinates), 1))))
849
+
850
+ # Call Polygram
851
+ super().__init__(vertices, **kwargs)
@@ -4,13 +4,25 @@ from __future__ import annotations
4
4
 
5
5
  __all__ = ["SurroundingRectangle", "BackgroundRectangle", "Cross", "Underline"]
6
6
 
7
- from manim import config, logger
8
- from manim.constants import *
7
+ from typing import Any
8
+
9
+ from typing_extensions import Self
10
+
11
+ from manim import logger
12
+ from manim._config import config
13
+ from manim.constants import (
14
+ DOWN,
15
+ LEFT,
16
+ RIGHT,
17
+ SMALL_BUFF,
18
+ UP,
19
+ )
9
20
  from manim.mobject.geometry.line import Line
10
21
  from manim.mobject.geometry.polygram import RoundedRectangle
11
22
  from manim.mobject.mobject import Mobject
23
+ from manim.mobject.opengl.opengl_mobject import OpenGLMobject
12
24
  from manim.mobject.types.vectorized_mobject import VGroup
13
- from manim.utils.color import BLACK, RED, YELLOW, Color, Colors
25
+ from manim.utils.color import BLACK, RED, YELLOW, ManimColor, ParsableManimColor
14
26
 
15
27
 
16
28
  class SurroundingRectangle(RoundedRectangle):
@@ -38,17 +50,36 @@ class SurroundingRectangle(RoundedRectangle):
38
50
  """
39
51
 
40
52
  def __init__(
41
- self, mobject, color=YELLOW, buff=SMALL_BUFF, corner_radius=0.0, **kwargs
42
- ):
53
+ self,
54
+ *mobjects: Mobject,
55
+ color: ParsableManimColor = YELLOW,
56
+ buff: float | tuple[float, float] = SMALL_BUFF,
57
+ corner_radius: float = 0.0,
58
+ **kwargs: Any,
59
+ ) -> None:
60
+ from manim.mobject.mobject import Group
61
+
62
+ if not all(isinstance(mob, (Mobject, OpenGLMobject)) for mob in mobjects):
63
+ raise TypeError(
64
+ "Expected all inputs for parameter mobjects to be a Mobjects"
65
+ )
66
+
67
+ if isinstance(buff, tuple):
68
+ buff_x = buff[0]
69
+ buff_y = buff[1]
70
+ else:
71
+ buff_x = buff_y = buff
72
+
73
+ group = Group(*mobjects)
43
74
  super().__init__(
44
75
  color=color,
45
- width=mobject.width + 2 * buff,
46
- height=mobject.height + 2 * buff,
76
+ width=group.width + 2 * buff_x,
77
+ height=group.height + 2 * buff_y,
47
78
  corner_radius=corner_radius,
48
79
  **kwargs,
49
80
  )
50
81
  self.buff = buff
51
- self.move_to(mobject)
82
+ self.move_to(group)
52
83
 
53
84
 
54
85
  class BackgroundRectangle(SurroundingRectangle):
@@ -78,19 +109,19 @@ class BackgroundRectangle(SurroundingRectangle):
78
109
 
79
110
  def __init__(
80
111
  self,
81
- mobject,
82
- color: Colors | None = None,
112
+ *mobjects: Mobject,
113
+ color: ParsableManimColor | None = None,
83
114
  stroke_width: float = 0,
84
115
  stroke_opacity: float = 0,
85
116
  fill_opacity: float = 0.75,
86
- buff: float = 0,
87
- **kwargs,
88
- ):
117
+ buff: float | tuple[float, float] = 0,
118
+ **kwargs: Any,
119
+ ) -> None:
89
120
  if color is None:
90
121
  color = config.background_color
91
122
 
92
123
  super().__init__(
93
- mobject,
124
+ *mobjects,
94
125
  color=color,
95
126
  stroke_width=stroke_width,
96
127
  stroke_opacity=stroke_opacity,
@@ -98,13 +129,13 @@ class BackgroundRectangle(SurroundingRectangle):
98
129
  buff=buff,
99
130
  **kwargs,
100
131
  )
101
- self.original_fill_opacity = self.fill_opacity
132
+ self.original_fill_opacity: float = self.fill_opacity
102
133
 
103
- def pointwise_become_partial(self, mobject, a, b):
134
+ def pointwise_become_partial(self, mobject: Mobject, a: Any, b: float) -> Self:
104
135
  self.set_fill(opacity=b * self.original_fill_opacity)
105
136
  return self
106
137
 
107
- def set_style(self, fill_opacity, **kwargs):
138
+ def set_style(self, fill_opacity: float, **kwargs: Any) -> Self: # type: ignore[override]
108
139
  # Unchangeable style, except for fill_opacity
109
140
  # All other style arguments are ignored
110
141
  super().set_style(
@@ -120,8 +151,11 @@ class BackgroundRectangle(SurroundingRectangle):
120
151
  )
121
152
  return self
122
153
 
123
- def get_fill_color(self):
124
- return Color(self.color)
154
+ def get_fill_color(self) -> ManimColor:
155
+ # The type of the color property is set to Any using the property decorator
156
+ # vectorized_mobject.py#L571
157
+ temp_color: ManimColor = self.color
158
+ return temp_color
125
159
 
126
160
 
127
161
  class Cross(VGroup):
@@ -152,11 +186,11 @@ class Cross(VGroup):
152
186
  def __init__(
153
187
  self,
154
188
  mobject: Mobject | None = None,
155
- stroke_color: Color = RED,
156
- stroke_width: float = 6,
157
- scale_factor: float = 1,
158
- **kwargs,
159
- ):
189
+ stroke_color: ParsableManimColor = RED,
190
+ stroke_width: float = 6.0,
191
+ scale_factor: float = 1.0,
192
+ **kwargs: Any,
193
+ ) -> None:
160
194
  super().__init__(
161
195
  Line(UP + LEFT, DOWN + RIGHT), Line(UP + RIGHT, DOWN + LEFT), **kwargs
162
196
  )
@@ -181,7 +215,9 @@ class Underline(Line):
181
215
  self.add(man, ul)
182
216
  """
183
217
 
184
- def __init__(self, mobject, buff=SMALL_BUFF, **kwargs):
218
+ def __init__(
219
+ self, mobject: Mobject, buff: float = SMALL_BUFF, **kwargs: Any
220
+ ) -> None:
185
221
  super().__init__(LEFT, RIGHT, buff=buff, **kwargs)
186
222
  self.match_width(mobject)
187
223
  self.next_to(mobject, DOWN, buff=self.buff)