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
@@ -40,22 +40,23 @@ __all__ = [
40
40
  "CubicBezier",
41
41
  "ArcPolygon",
42
42
  "ArcPolygonFromArcs",
43
+ "TangentialArc",
43
44
  ]
44
45
 
45
46
  import itertools
46
- import math
47
47
  import warnings
48
- from typing import TYPE_CHECKING, Sequence
48
+ from typing import TYPE_CHECKING, Any, cast
49
49
 
50
50
  import numpy as np
51
- from colour import Color
51
+ from typing_extensions import Self
52
52
 
53
53
  from manim.constants import *
54
54
  from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL
55
- from manim.mobject.types.vectorized_mobject import VMobject
56
- from manim.utils.color import *
55
+ from manim.mobject.types.vectorized_mobject import VGroup, VMobject
56
+ from manim.utils.color import BLACK, BLUE, RED, WHITE, ParsableManimColor
57
57
  from manim.utils.iterables import adjacent_pairs
58
58
  from manim.utils.space_ops import (
59
+ angle_between_vectors,
59
60
  angle_of_vector,
60
61
  cartesian_to_spherical,
61
62
  line_intersection,
@@ -64,9 +65,19 @@ from manim.utils.space_ops import (
64
65
  )
65
66
 
66
67
  if TYPE_CHECKING:
68
+ from collections.abc import Iterable
69
+
70
+ import manim.mobject.geometry.tips as tips
71
+ from manim.mobject.geometry.line import Line
67
72
  from manim.mobject.mobject import Mobject
68
73
  from manim.mobject.text.tex_mobject import SingleStringMathTex, Tex
69
74
  from manim.mobject.text.text_mobject import Text
75
+ from manim.typing import (
76
+ Point3D,
77
+ Point3DLike,
78
+ QuadraticSpline,
79
+ Vector3DLike,
80
+ )
70
81
 
71
82
 
72
83
  class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
@@ -89,21 +100,26 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
89
100
 
90
101
  def __init__(
91
102
  self,
92
- tip_length=DEFAULT_ARROW_TIP_LENGTH,
93
- normal_vector=OUT,
94
- tip_style={},
95
- **kwargs,
96
- ):
97
- self.tip_length = tip_length
103
+ tip_length: float = DEFAULT_ARROW_TIP_LENGTH,
104
+ normal_vector: Vector3DLike = OUT,
105
+ tip_style: dict = {},
106
+ **kwargs: Any,
107
+ ) -> None:
108
+ self.tip_length: float = tip_length
98
109
  self.normal_vector = normal_vector
99
- self.tip_style = tip_style
110
+ self.tip_style: dict = tip_style
100
111
  super().__init__(**kwargs)
101
112
 
102
113
  # Adding, Creating, Modifying tips
103
114
 
104
115
  def add_tip(
105
- self, tip=None, tip_shape=None, tip_length=None, tip_width=None, at_start=False
106
- ):
116
+ self,
117
+ tip: tips.ArrowTip | None = None,
118
+ tip_shape: type[tips.ArrowTip] | None = None,
119
+ tip_length: float | None = None,
120
+ tip_width: float | None = None,
121
+ at_start: bool = False,
122
+ ) -> Self:
107
123
  """Adds a tip to the TipableVMobject instance, recognising
108
124
  that the endpoints might need to be switched if it's
109
125
  a 'starting tip' or not.
@@ -118,8 +134,12 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
118
134
  return self
119
135
 
120
136
  def create_tip(
121
- self, tip_shape=None, tip_length=None, tip_width=None, at_start=False
122
- ):
137
+ self,
138
+ tip_shape: type[tips.ArrowTip] | None = None,
139
+ tip_length: float | None = None,
140
+ tip_width: float | None = None,
141
+ at_start: bool = False,
142
+ ) -> tips.ArrowTip:
123
143
  """Stylises the tip, positions it spatially, and returns
124
144
  the newly instantiated tip to the caller.
125
145
  """
@@ -127,13 +147,18 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
127
147
  self.position_tip(tip, at_start)
128
148
  return tip
129
149
 
130
- def get_unpositioned_tip(self, tip_shape=None, tip_length=None, tip_width=None):
150
+ def get_unpositioned_tip(
151
+ self,
152
+ tip_shape: type[tips.ArrowTip] | None = None,
153
+ tip_length: float | None = None,
154
+ tip_width: float | None = None,
155
+ ) -> tips.ArrowTip | tips.ArrowTriangleFilledTip:
131
156
  """Returns a tip that has been stylistically configured,
132
157
  but has not yet been given a position in space.
133
158
  """
134
159
  from manim.mobject.geometry.tips import ArrowTriangleFilledTip
135
160
 
136
- style = {}
161
+ style: dict[str, Any] = {}
137
162
 
138
163
  if tip_shape is None:
139
164
  tip_shape = ArrowTriangleFilledTip
@@ -151,7 +176,7 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
151
176
  tip = tip_shape(length=tip_length, **style)
152
177
  return tip
153
178
 
154
- def position_tip(self, tip, at_start=False):
179
+ def position_tip(self, tip: tips.ArrowTip, at_start: bool = False) -> tips.ArrowTip:
155
180
  # Last two control points, defining both
156
181
  # the end, and the tangency direction
157
182
  if at_start:
@@ -165,11 +190,13 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
165
190
  angles[1] - PI - tip.tip_angle,
166
191
  ) # Rotates the tip along the azimuthal
167
192
  if not hasattr(self, "_init_positioning_axis"):
168
- axis = [
169
- np.sin(angles[1]),
170
- -np.cos(angles[1]),
171
- 0,
172
- ] # Obtains the perpendicular of the tip
193
+ axis = np.array(
194
+ [
195
+ np.sin(angles[1]),
196
+ -np.cos(angles[1]),
197
+ 0,
198
+ ]
199
+ ) # Obtains the perpendicular of the tip
173
200
  tip.rotate(
174
201
  -angles[2] + PI / 2,
175
202
  axis=axis,
@@ -178,7 +205,7 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
178
205
  tip.shift(anchor - tip.tip_point)
179
206
  return tip
180
207
 
181
- def reset_endpoints_based_on_tip(self, tip, at_start):
208
+ def reset_endpoints_based_on_tip(self, tip: tips.ArrowTip, at_start: bool) -> Self:
182
209
  if self.get_length() == 0:
183
210
  # Zero length, put_start_and_end_on wouldn't work
184
211
  return self
@@ -189,7 +216,7 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
189
216
  self.put_start_and_end_on(self.get_start(), tip.base)
190
217
  return self
191
218
 
192
- def asign_tip_attr(self, tip, at_start):
219
+ def asign_tip_attr(self, tip: tips.ArrowTip, at_start: bool) -> Self:
193
220
  if at_start:
194
221
  self.start_tip = tip
195
222
  else:
@@ -198,15 +225,15 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
198
225
 
199
226
  # Checking for tips
200
227
 
201
- def has_tip(self):
228
+ def has_tip(self) -> bool:
202
229
  return hasattr(self, "tip") and self.tip in self
203
230
 
204
- def has_start_tip(self):
231
+ def has_start_tip(self) -> bool:
205
232
  return hasattr(self, "start_tip") and self.start_tip in self
206
233
 
207
234
  # Getters
208
235
 
209
- def pop_tips(self):
236
+ def pop_tips(self) -> VGroup:
210
237
  start, end = self.get_start_and_end()
211
238
  result = self.get_group_class()()
212
239
  if self.has_tip():
@@ -218,7 +245,7 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
218
245
  self.put_start_and_end_on(start, end)
219
246
  return result
220
247
 
221
- def get_tips(self):
248
+ def get_tips(self) -> VGroup:
222
249
  """Returns a VGroup (collection of VMobjects) containing
223
250
  the TipableVMObject instance's tips.
224
251
  """
@@ -229,39 +256,49 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
229
256
  result.add(self.start_tip)
230
257
  return result
231
258
 
232
- def get_tip(self):
259
+ def get_tip(self) -> VMobject:
233
260
  """Returns the TipableVMobject instance's (first) tip,
234
- otherwise throws an exception."""
261
+ otherwise throws an exception.
262
+ """
235
263
  tips = self.get_tips()
236
264
  if len(tips) == 0:
237
265
  raise Exception("tip not found")
238
266
  else:
239
- return tips[0]
267
+ tip: VMobject = tips[0]
268
+ return tip
240
269
 
241
- def get_default_tip_length(self):
270
+ def get_default_tip_length(self) -> float:
242
271
  return self.tip_length
243
272
 
244
- def get_first_handle(self):
245
- return self.points[1]
246
-
247
- def get_last_handle(self):
248
- return self.points[-2]
249
-
250
- def get_end(self):
273
+ def get_first_handle(self) -> Point3D:
274
+ # Type inference of extracting an element from a list, is not
275
+ # supported by numpy, see this numpy issue
276
+ # https://github.com/numpy/numpy/issues/16544
277
+ first_handle: Point3D = self.points[1]
278
+ return first_handle
279
+
280
+ def get_last_handle(self) -> Point3D:
281
+ # Type inference of extracting an element from a list, is not
282
+ # supported by numpy, see this numpy issue
283
+ # https://github.com/numpy/numpy/issues/16544
284
+ last_handle: Point3D = self.points[-2]
285
+ return last_handle
286
+
287
+ def get_end(self) -> Point3D:
251
288
  if self.has_tip():
252
289
  return self.tip.get_start()
253
290
  else:
254
291
  return super().get_end()
255
292
 
256
- def get_start(self):
293
+ def get_start(self) -> Point3D:
257
294
  if self.has_start_tip():
258
295
  return self.start_tip.get_start()
259
296
  else:
260
297
  return super().get_start()
261
298
 
262
- def get_length(self):
299
+ def get_length(self) -> float:
263
300
  start, end = self.get_start_and_end()
264
- return np.linalg.norm(start - end)
301
+ return float(np.linalg.norm(start - end))
265
302
 
266
303
 
267
304
  class Arc(TipableVMobject):
@@ -281,24 +318,24 @@ class Arc(TipableVMobject):
281
318
 
282
319
  def __init__(
283
320
  self,
284
- radius: float = 1.0,
285
- start_angle=0,
286
- angle=TAU / 4,
287
- num_components=9,
288
- arc_center=ORIGIN,
289
- **kwargs,
321
+ radius: float | None = 1.0,
322
+ start_angle: float = 0,
323
+ angle: float = TAU / 4,
324
+ num_components: int = 9,
325
+ arc_center: Point3DLike = ORIGIN,
326
+ **kwargs: Any,
290
327
  ):
291
328
  if radius is None: # apparently None is passed by ArcBetweenPoints
292
329
  radius = 1.0
293
330
  self.radius = radius
294
331
  self.num_components = num_components
295
- self.arc_center = arc_center
332
+ self.arc_center: Point3D = np.asarray(arc_center)
296
333
  self.start_angle = start_angle
297
334
  self.angle = angle
298
- self._failed_to_get_center = False
335
+ self._failed_to_get_center: bool = False
299
336
  super().__init__(**kwargs)
300
337
 
301
- def generate_points(self):
338
+ def generate_points(self) -> None:
302
339
  self._set_pre_positioned_points()
303
340
  self.scale(self.radius, about_point=ORIGIN)
304
341
  self.shift(self.arc_center)
@@ -306,7 +343,7 @@ class Arc(TipableVMobject):
306
343
  # Points are set a bit differently when rendering via OpenGL.
307
344
  # TODO: refactor Arc so that only one strategy for setting points
308
345
  # has to be used.
309
- def init_points(self):
346
+ def init_points(self) -> None:
310
347
  self.set_points(
311
348
  Arc._create_quadratic_bezier_points(
312
349
  angle=self.angle,
@@ -318,7 +355,9 @@ class Arc(TipableVMobject):
318
355
  self.shift(self.arc_center)
319
356
 
320
357
  @staticmethod
321
- def _create_quadratic_bezier_points(angle, start_angle=0, n_components=8):
358
+ def _create_quadratic_bezier_points(
359
+ angle: float, start_angle: float = 0, n_components: int = 8
360
+ ) -> QuadraticSpline:
322
361
  samples = np.array(
323
362
  [
324
363
  [np.cos(a), np.sin(a), 0]
@@ -338,7 +377,7 @@ class Arc(TipableVMobject):
338
377
  points[2::3] = samples[2::2]
339
378
  return points
340
379
 
341
- def _set_pre_positioned_points(self):
380
+ def _set_pre_positioned_points(self) -> None:
342
381
  anchors = np.array(
343
382
  [
344
383
  np.cos(a) * RIGHT + np.sin(a) * UP
@@ -357,11 +396,12 @@ class Arc(TipableVMobject):
357
396
  tangent_vectors[:, 1] = anchors[:, 0]
358
397
  tangent_vectors[:, 0] = -anchors[:, 1]
359
398
  # Use tangent vectors to deduce anchors
360
- handles1 = anchors[:-1] + (d_theta / 3) * tangent_vectors[:-1]
361
- handles2 = anchors[1:] - (d_theta / 3) * tangent_vectors[1:]
399
+ factor = 4 / 3 * np.tan(d_theta / 4)
400
+ handles1 = anchors[:-1] + factor * tangent_vectors[:-1]
401
+ handles2 = anchors[1:] - factor * tangent_vectors[1:]
362
402
  self.set_anchors_and_handles(anchors[:-1], handles1, handles2, anchors[1:])
363
403
 
364
- def get_arc_center(self, warning=True):
404
+ def get_arc_center(self, warning: bool = True) -> Point3D:
365
405
  """Looks at the normals to the first two
366
406
  anchors, and finds their intersection points
367
407
  """
@@ -372,7 +412,7 @@ class Arc(TipableVMobject):
372
412
  # For a1 and a2 to lie at the same point arc radius
373
413
  # must be zero. Thus arc_center will also lie at
374
414
  # that point.
375
- return a1
415
+ return np.copy(a1)
376
416
  # Tangent vectors
377
417
  t1 = h1 - a1
378
418
  t2 = h2 - a2
@@ -383,16 +423,21 @@ class Arc(TipableVMobject):
383
423
  return line_intersection(line1=(a1, a1 + n1), line2=(a2, a2 + n2))
384
424
  except Exception:
385
425
  if warning:
386
- warnings.warn("Can't find Arc center, using ORIGIN instead")
426
+ warnings.warn(
427
+ "Can't find Arc center, using ORIGIN instead", stacklevel=1
428
+ )
387
429
  self._failed_to_get_center = True
388
430
  return np.array(ORIGIN)
389
431
 
390
- def move_arc_center_to(self, point):
432
+ def move_arc_center_to(self, point: Point3DLike) -> Self:
391
433
  self.shift(point - self.get_arc_center())
392
434
  return self
393
435
 
394
- def stop_angle(self):
395
- return angle_of_vector(self.points[-1] - self.get_arc_center()) % TAU
436
+ def stop_angle(self) -> float:
437
+ return cast(
438
+ float,
439
+ angle_of_vector(self.points[-1] - self.get_arc_center()) % TAU,
440
+ )
396
441
 
397
442
 
398
443
  class ArcBetweenPoints(Arc):
@@ -414,7 +459,14 @@ class ArcBetweenPoints(Arc):
414
459
  self.play(Create(arc))
415
460
  """
416
461
 
417
- def __init__(self, start, end, angle=TAU / 4, radius=None, **kwargs):
462
+ def __init__(
463
+ self,
464
+ start: Point3DLike,
465
+ end: Point3DLike,
466
+ angle: float = TAU / 4,
467
+ radius: float | None = None,
468
+ **kwargs: Any,
469
+ ) -> None:
418
470
  if radius is not None:
419
471
  self.radius = radius
420
472
  if radius < 0:
@@ -428,24 +480,94 @@ class ArcBetweenPoints(Arc):
428
480
  """ArcBetweenPoints called with a radius that is
429
481
  smaller than half the distance between the points.""",
430
482
  )
431
- arc_height = radius - math.sqrt(radius**2 - halfdist**2)
432
- angle = math.acos((radius - arc_height) / radius) * sign
483
+ arc_height = radius - np.sqrt(radius**2 - halfdist**2)
484
+ angle = np.arccos((radius - arc_height) / radius) * sign
433
485
 
434
486
  super().__init__(radius=radius, angle=angle, **kwargs)
435
487
  if angle == 0:
436
- self.set_points_as_corners([LEFT, RIGHT])
488
+ self.set_points_as_corners(np.array([LEFT, RIGHT]))
437
489
  self.put_start_and_end_on(start, end)
438
490
 
439
491
  if radius is None:
440
492
  center = self.get_arc_center(warning=False)
441
493
  if not self._failed_to_get_center:
442
- self.radius = np.linalg.norm(np.array(start) - np.array(center))
494
+ # np.linalg.norm returns floating[Any] which is not compatible with float
495
+ self.radius = cast(
496
+ float, np.linalg.norm(np.array(start) - np.array(center))
497
+ )
443
498
  else:
444
- self.radius = math.inf
499
+ self.radius = np.inf
500
+
501
+
502
+ class TangentialArc(ArcBetweenPoints):
503
+ """
504
+ Construct an arc that is tangent to two intersecting lines.
505
+ You can choose any of the 4 possible corner arcs via the `corner` tuple.
506
+ corner = (s1, s2) where each si is ±1 to control direction along each line.
507
+
508
+ Example
509
+ -------
510
+ .. manim:: TangentialArcExample
511
+
512
+ class TangentialArcExample(Scene):
513
+ def construct(self):
514
+ line1 = DashedLine(start=3 * LEFT, end=3 * RIGHT)
515
+ line1.rotate(angle=31 * DEGREES, about_point=ORIGIN)
516
+ line2 = DashedLine(start=3 * UP, end=3 * DOWN)
517
+ line2.rotate(angle=12 * DEGREES, about_point=ORIGIN)
518
+
519
+ arc = TangentialArc(line1, line2, radius=2.25, corner=(1, 1), color=TEAL)
520
+ self.add(arc, line1, line2)
521
+ """
522
+
523
+ def __init__(
524
+ self,
525
+ line1: Line,
526
+ line2: Line,
527
+ radius: float,
528
+ corner: Any = (1, 1),
529
+ **kwargs: Any,
530
+ ):
531
+ self.line1 = line1
532
+ self.line2 = line2
533
+
534
+ intersection_point = line_intersection(
535
+ [line1.get_start(), line1.get_end()], [line2.get_start(), line2.get_end()]
536
+ )
537
+
538
+ s1, s2 = corner
539
+ # Get unit vector for specified directions
540
+ unit_vector1 = s1 * line1.get_unit_vector()
541
+ unit_vector2 = s2 * line2.get_unit_vector()
542
+
543
+ corner_angle = angle_between_vectors(unit_vector1, unit_vector2)
544
+ tangent_point_distance = radius / np.tan(corner_angle / 2)
545
+
546
+ # tangent points
547
+ tangent_point1 = intersection_point + tangent_point_distance * unit_vector1
548
+ tangent_point2 = intersection_point + tangent_point_distance * unit_vector2
549
+
550
+ cross_product = (
551
+ unit_vector1[0] * unit_vector2[1] - unit_vector1[1] * unit_vector2[0]
552
+ )
553
+
554
+ # Determine start and end points based on orientation
555
+ if cross_product < 0:
556
+ # Counterclockwise orientation - standard order
557
+ start_point = tangent_point1
558
+ end_point = tangent_point2
559
+ else:
560
+ # Clockwise orientation - reverse the points
561
+ start_point = tangent_point2
562
+ end_point = tangent_point1
563
+
564
+ super().__init__(start=start_point, end=end_point, radius=radius, **kwargs)
445
565
 
446
566
 
447
567
  class CurvedArrow(ArcBetweenPoints):
448
- def __init__(self, start_point, end_point, **kwargs):
568
+ def __init__(
569
+ self, start_point: Point3DLike, end_point: Point3DLike, **kwargs: Any
570
+ ) -> None:
449
571
  from manim.mobject.geometry.tips import ArrowTriangleFilledTip
450
572
 
451
573
  tip_shape = kwargs.pop("tip_shape", ArrowTriangleFilledTip)
@@ -454,7 +576,9 @@ class CurvedArrow(ArcBetweenPoints):
454
576
 
455
577
 
456
578
  class CurvedDoubleArrow(CurvedArrow):
457
- def __init__(self, start_point, end_point, **kwargs):
579
+ def __init__(
580
+ self, start_point: Point3DLike, end_point: Point3DLike, **kwargs: Any
581
+ ) -> None:
458
582
  if "tip_shape_end" in kwargs:
459
583
  kwargs["tip_shape"] = kwargs.pop("tip_shape_end")
460
584
  from manim.mobject.geometry.tips import ArrowTriangleFilledTip
@@ -492,9 +616,9 @@ class Circle(Arc):
492
616
  def __init__(
493
617
  self,
494
618
  radius: float | None = None,
495
- color: Color | str = RED,
496
- **kwargs,
497
- ):
619
+ color: ParsableManimColor = RED,
620
+ **kwargs: Any,
621
+ ) -> None:
498
622
  super().__init__(
499
623
  radius=radius,
500
624
  start_angle=0,
@@ -509,7 +633,7 @@ class Circle(Arc):
509
633
  dim_to_match: int = 0,
510
634
  stretch: bool = False,
511
635
  buffer_factor: float = 1.2,
512
- ):
636
+ ) -> Self:
513
637
  """Modifies a circle so that it surrounds a given mobject.
514
638
 
515
639
  Parameters
@@ -545,7 +669,6 @@ class Circle(Arc):
545
669
  group = Group(group1, group2, group3).arrange(buff=1)
546
670
  self.add(group)
547
671
  """
548
-
549
672
  # Ignores dim_to_match and stretch; result will always be a circle
550
673
  # TODO: Perhaps create an ellipse class to handle single-dimension stretching
551
674
 
@@ -556,7 +679,7 @@ class Circle(Arc):
556
679
  self.width = np.sqrt(mobject.width**2 + mobject.height**2)
557
680
  return self.scale(buffer_factor)
558
681
 
559
- def point_at_angle(self, angle: float):
682
+ def point_at_angle(self, angle: float) -> Point3D:
560
683
  """Returns the position of a point on the circle.
561
684
 
562
685
  Parameters
@@ -585,16 +708,15 @@ class Circle(Arc):
585
708
  self.add(circle, s1, s2)
586
709
 
587
710
  """
588
-
589
711
  start_angle = angle_of_vector(self.points[0] - self.get_center())
590
712
  proportion = (angle - start_angle) / TAU
591
- proportion -= math.floor(proportion)
713
+ proportion -= np.floor(proportion)
592
714
  return self.point_from_proportion(proportion)
593
715
 
594
716
  @staticmethod
595
717
  def from_three_points(
596
- p1: Sequence[float], p2: Sequence[float], p3: Sequence[float], **kwargs
597
- ):
718
+ p1: Point3DLike, p2: Point3DLike, p3: Point3DLike, **kwargs: Any
719
+ ) -> Circle:
598
720
  """Returns a circle passing through the specified
599
721
  three points.
600
722
 
@@ -614,10 +736,11 @@ class Circle(Arc):
614
736
  self.add(NumberPlane(), circle, dots)
615
737
  """
616
738
  center = line_intersection(
617
- perpendicular_bisector([p1, p2]),
618
- perpendicular_bisector([p2, p3]),
739
+ perpendicular_bisector([np.asarray(p1), np.asarray(p2)]),
740
+ perpendicular_bisector([np.asarray(p2), np.asarray(p3)]),
619
741
  )
620
- radius = np.linalg.norm(p1 - center)
742
+ # np.linalg.norm returns floating[Any] which is not compatible with float
743
+ radius = cast(float, np.linalg.norm(p1 - center))
621
744
  return Circle(radius=radius, **kwargs).shift(center)
622
745
 
623
746
 
@@ -654,13 +777,13 @@ class Dot(Circle):
654
777
 
655
778
  def __init__(
656
779
  self,
657
- point: list | np.ndarray = ORIGIN,
780
+ point: Point3DLike = ORIGIN,
658
781
  radius: float = DEFAULT_DOT_RADIUS,
659
782
  stroke_width: float = 0,
660
783
  fill_opacity: float = 1.0,
661
- color: Color | str = WHITE,
662
- **kwargs,
663
- ):
784
+ color: ParsableManimColor = WHITE,
785
+ **kwargs: Any,
786
+ ) -> None:
664
787
  super().__init__(
665
788
  arc_center=point,
666
789
  radius=radius,
@@ -677,11 +800,11 @@ class AnnotationDot(Dot):
677
800
  def __init__(
678
801
  self,
679
802
  radius: float = DEFAULT_DOT_RADIUS * 1.3,
680
- stroke_width=5,
681
- stroke_color=WHITE,
682
- fill_color=BLUE,
683
- **kwargs,
684
- ):
803
+ stroke_width: float = 5,
804
+ stroke_color: ParsableManimColor = WHITE,
805
+ fill_color: ParsableManimColor = BLUE,
806
+ **kwargs: Any,
807
+ ) -> None:
685
808
  super().__init__(
686
809
  radius=radius,
687
810
  stroke_width=stroke_width,
@@ -702,8 +825,9 @@ class LabeledDot(Dot):
702
825
  representing rendered strings like :class:`~.Text` or :class:`~.Tex`
703
826
  can be passed as well.
704
827
  radius
705
- The radius of the :class:`Dot`. If ``None`` (the default), the radius
706
- is calculated based on the size of the ``label``.
828
+ The radius of the :class:`Dot`. If provided, the ``buff`` is ignored.
829
+ If ``None`` (the default), the radius is calculated based on the size
830
+ of the ``label`` and the ``buff``.
707
831
 
708
832
  Examples
709
833
  --------
@@ -729,17 +853,20 @@ class LabeledDot(Dot):
729
853
  self,
730
854
  label: str | SingleStringMathTex | Text | Tex,
731
855
  radius: float | None = None,
732
- **kwargs,
856
+ buff: float = SMALL_BUFF,
857
+ **kwargs: Any,
733
858
  ) -> None:
734
859
  if isinstance(label, str):
735
860
  from manim import MathTex
736
861
 
737
- rendered_label = MathTex(label, color=BLACK)
862
+ rendered_label: VMobject = MathTex(label, color=BLACK)
738
863
  else:
739
864
  rendered_label = label
740
865
 
741
866
  if radius is None:
742
- radius = 0.1 + max(rendered_label.width, rendered_label.height) / 2
867
+ radius = buff + float(
868
+ np.linalg.norm([rendered_label.width, rendered_label.height]) / 2
869
+ )
743
870
  super().__init__(radius=radius, **kwargs)
744
871
  rendered_label.move_to(self.get_center())
745
872
  self.add(rendered_label)
@@ -770,14 +897,16 @@ class Ellipse(Circle):
770
897
  self.add(ellipse_group)
771
898
  """
772
899
 
773
- def __init__(self, width: float = 2, height: float = 1, **kwargs):
900
+ def __init__(self, width: float = 2, height: float = 1, **kwargs: Any) -> None:
774
901
  super().__init__(**kwargs)
775
902
  self.stretch_to_fit_width(width)
776
903
  self.stretch_to_fit_height(height)
777
904
 
778
905
 
779
906
  class AnnularSector(Arc):
780
- """
907
+ """A sector of an annulus.
908
+
909
+
781
910
  Parameters
782
911
  ----------
783
912
  inner_radius
@@ -822,15 +951,15 @@ class AnnularSector(Arc):
822
951
 
823
952
  def __init__(
824
953
  self,
825
- inner_radius=1,
826
- outer_radius=2,
827
- angle=TAU / 4,
828
- start_angle=0,
829
- fill_opacity=1,
830
- stroke_width=0,
831
- color=WHITE,
832
- **kwargs,
833
- ):
954
+ inner_radius: float = 1,
955
+ outer_radius: float = 2,
956
+ angle: float = TAU / 4,
957
+ start_angle: float = 0,
958
+ fill_opacity: float = 1,
959
+ stroke_width: float = 0,
960
+ color: ParsableManimColor = WHITE,
961
+ **kwargs: Any,
962
+ ) -> None:
834
963
  self.inner_radius = inner_radius
835
964
  self.outer_radius = outer_radius
836
965
  super().__init__(
@@ -842,7 +971,7 @@ class AnnularSector(Arc):
842
971
  **kwargs,
843
972
  )
844
973
 
845
- def generate_points(self):
974
+ def generate_points(self) -> None:
846
975
  inner_arc, outer_arc = (
847
976
  Arc(
848
977
  start_angle=self.start_angle,
@@ -858,11 +987,13 @@ class AnnularSector(Arc):
858
987
  self.append_points(outer_arc.points)
859
988
  self.add_line_to(inner_arc.points[0])
860
989
 
861
- init_points = generate_points
990
+ def init_points(self) -> None:
991
+ self.generate_points()
862
992
 
863
993
 
864
994
  class Sector(AnnularSector):
865
- """
995
+ """A sector of a circle.
996
+
866
997
  Examples
867
998
  --------
868
999
  .. manim:: ExampleSector
@@ -870,15 +1001,15 @@ class Sector(AnnularSector):
870
1001
 
871
1002
  class ExampleSector(Scene):
872
1003
  def construct(self):
873
- sector = Sector(outer_radius=2, inner_radius=1)
874
- sector2 = Sector(outer_radius=2.5, inner_radius=0.8).move_to([-3, 0, 0])
1004
+ sector = Sector(radius=2)
1005
+ sector2 = Sector(radius=2.5, angle=60*DEGREES).move_to([-3, 0, 0])
875
1006
  sector.set_color(RED)
876
1007
  sector2.set_color(PINK)
877
1008
  self.add(sector, sector2)
878
1009
  """
879
1010
 
880
- def __init__(self, outer_radius=1, inner_radius=0, **kwargs):
881
- super().__init__(inner_radius=inner_radius, outer_radius=outer_radius, **kwargs)
1011
+ def __init__(self, radius: float = 1, **kwargs: Any) -> None:
1012
+ super().__init__(inner_radius=0, outer_radius=radius, **kwargs)
882
1013
 
883
1014
 
884
1015
  class Annulus(Circle):
@@ -907,14 +1038,14 @@ class Annulus(Circle):
907
1038
 
908
1039
  def __init__(
909
1040
  self,
910
- inner_radius: float | None = 1,
911
- outer_radius: float | None = 2,
912
- fill_opacity=1,
913
- stroke_width=0,
914
- color=WHITE,
915
- mark_paths_closed=False,
916
- **kwargs,
917
- ):
1041
+ inner_radius: float = 1,
1042
+ outer_radius: float = 2,
1043
+ fill_opacity: float = 1,
1044
+ stroke_width: float = 0,
1045
+ color: ParsableManimColor = WHITE,
1046
+ mark_paths_closed: bool = False,
1047
+ **kwargs: Any,
1048
+ ) -> None:
918
1049
  self.mark_paths_closed = mark_paths_closed # is this even used?
919
1050
  self.inner_radius = inner_radius
920
1051
  self.outer_radius = outer_radius
@@ -922,7 +1053,7 @@ class Annulus(Circle):
922
1053
  fill_opacity=fill_opacity, stroke_width=stroke_width, color=color, **kwargs
923
1054
  )
924
1055
 
925
- def generate_points(self):
1056
+ def generate_points(self) -> None:
926
1057
  self.radius = self.outer_radius
927
1058
  outer_circle = Circle(radius=self.outer_radius)
928
1059
  inner_circle = Circle(radius=self.inner_radius)
@@ -931,11 +1062,13 @@ class Annulus(Circle):
931
1062
  self.append_points(inner_circle.points)
932
1063
  self.shift(self.arc_center)
933
1064
 
934
- init_points = generate_points
1065
+ def init_points(self) -> None:
1066
+ self.generate_points()
935
1067
 
936
1068
 
937
1069
  class CubicBezier(VMobject, metaclass=ConvertToOpenGL):
938
- """
1070
+ """A cubic Bézier curve.
1071
+
939
1072
  Example
940
1073
  -------
941
1074
  .. manim:: BezierSplineExample
@@ -956,7 +1089,14 @@ class CubicBezier(VMobject, metaclass=ConvertToOpenGL):
956
1089
 
957
1090
  """
958
1091
 
959
- def __init__(self, start_anchor, start_handle, end_handle, end_anchor, **kwargs):
1092
+ def __init__(
1093
+ self,
1094
+ start_anchor: Point3DLike,
1095
+ start_handle: Point3DLike,
1096
+ end_handle: Point3DLike,
1097
+ end_anchor: Point3DLike,
1098
+ **kwargs: Any,
1099
+ ) -> None:
960
1100
  super().__init__(**kwargs)
961
1101
  self.add_cubic_bezier_curve(start_anchor, start_handle, end_handle, end_anchor)
962
1102
 
@@ -1042,18 +1182,20 @@ class ArcPolygon(VMobject, metaclass=ConvertToOpenGL):
1042
1182
 
1043
1183
  def __init__(
1044
1184
  self,
1045
- *vertices: list | np.ndarray,
1185
+ *vertices: Point3DLike,
1046
1186
  angle: float = PI / 4,
1047
1187
  radius: float | None = None,
1048
1188
  arc_config: list[dict] | None = None,
1049
- **kwargs,
1050
- ):
1189
+ **kwargs: Any,
1190
+ ) -> None:
1051
1191
  n = len(vertices)
1052
1192
  point_pairs = [(vertices[k], vertices[(k + 1) % n]) for k in range(n)]
1053
1193
 
1054
1194
  if not arc_config:
1055
1195
  if radius:
1056
- all_arc_configs = itertools.repeat({"radius": radius}, len(point_pairs))
1196
+ all_arc_configs: Iterable[dict] = itertools.repeat(
1197
+ {"radius": radius}, len(point_pairs)
1198
+ )
1057
1199
  else:
1058
1200
  all_arc_configs = itertools.repeat({"angle": angle}, len(point_pairs))
1059
1201
  elif isinstance(arc_config, dict):
@@ -1185,7 +1327,7 @@ class ArcPolygonFromArcs(VMobject, metaclass=ConvertToOpenGL):
1185
1327
  self.wait(2)
1186
1328
  """
1187
1329
 
1188
- def __init__(self, *arcs: Arc | ArcBetweenPoints, **kwargs):
1330
+ def __init__(self, *arcs: Arc | ArcBetweenPoints, **kwargs: Any) -> None:
1189
1331
  if not all(isinstance(m, (Arc, ArcBetweenPoints)) for m in arcs):
1190
1332
  raise ValueError(
1191
1333
  "All ArcPolygon submobjects must be of type Arc/ArcBetweenPoints",
@@ -1204,7 +1346,7 @@ class ArcPolygonFromArcs(VMobject, metaclass=ConvertToOpenGL):
1204
1346
  self.append_points(arc1.points)
1205
1347
  line = Line(arc1.get_end(), arc2.get_start())
1206
1348
  len_ratio = line.get_length() / arc1.get_arc_length()
1207
- if math.isnan(len_ratio) or math.isinf(len_ratio):
1349
+ if np.isnan(len_ratio) or np.isinf(len_ratio):
1208
1350
  continue
1209
1351
  line.insert_n_curves(int(arc1.get_num_curves() * len_ratio))
1210
1352
  self.append_points(line.points)