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
@@ -5,7 +5,8 @@ from __future__ import annotations
5
5
  __all__ = ["ParametricFunction", "FunctionGraph", "ImplicitFunction"]
6
6
 
7
7
 
8
- from typing import Callable, Iterable, Sequence
8
+ from collections.abc import Callable, Iterable, Sequence
9
+ from typing import TYPE_CHECKING
9
10
 
10
11
  import numpy as np
11
12
  from isosurfaces import plot_isoline
@@ -14,6 +15,15 @@ from manim import config
14
15
  from manim.mobject.graphing.scale import LinearBase, _ScaleBase
15
16
  from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL
16
17
  from manim.mobject.types.vectorized_mobject import VMobject
18
+
19
+ if TYPE_CHECKING:
20
+ from typing import Any
21
+
22
+ from typing_extensions import Self
23
+
24
+ from manim.typing import Point3D, Point3DLike
25
+ from manim.utils.color import ParsableManimColor
26
+
17
27
  from manim.utils.color import YELLOW
18
28
 
19
29
 
@@ -23,9 +33,9 @@ class ParametricFunction(VMobject, metaclass=ConvertToOpenGL):
23
33
  Parameters
24
34
  ----------
25
35
  function
26
- The function to be plotted in the form of ``(lambda x: x**2)``
36
+ The function to be plotted in the form of ``(lambda t: (x(t), y(t), z(t)))``
27
37
  t_range
28
- Determines the length that the function spans. By default ``[0, 1]``
38
+ Determines the length that the function spans in the form of (t_min, t_max, step=0.01). By default ``[0, 1]``
29
39
  scaling
30
40
  Scaling class applied to the points of the function. Default of :class:`~.LinearBase`.
31
41
  use_smoothing
@@ -49,10 +59,10 @@ class ParametricFunction(VMobject, metaclass=ConvertToOpenGL):
49
59
 
50
60
  class PlotParametricFunction(Scene):
51
61
  def func(self, t):
52
- return np.array((np.sin(2 * t), np.sin(3 * t), 0))
62
+ return (np.sin(2 * t), np.sin(3 * t), 0)
53
63
 
54
64
  def construct(self):
55
- func = ParametricFunction(self.func, t_range = np.array([0, TAU]), fill_opacity=0).set_color(RED)
65
+ func = ParametricFunction(self.func, t_range = (0, TAU), fill_opacity=0).set_color(RED)
56
66
  self.add(func.scale(3))
57
67
 
58
68
  .. manim:: ThreeDParametricSpring
@@ -61,11 +71,11 @@ class ParametricFunction(VMobject, metaclass=ConvertToOpenGL):
61
71
  class ThreeDParametricSpring(ThreeDScene):
62
72
  def construct(self):
63
73
  curve1 = ParametricFunction(
64
- lambda u: np.array([
74
+ lambda u: (
65
75
  1.2 * np.cos(u),
66
76
  1.2 * np.sin(u),
67
77
  u * 0.05
68
- ]), color=RED, t_range = np.array([-3*TAU, 5*TAU, 0.01])
78
+ ), color=RED, t_range = (-3*TAU, 5*TAU, 0.01)
69
79
  ).set_shade_in_3d(True)
70
80
  axes = ThreeDAxes()
71
81
  self.add(axes, curve1)
@@ -97,19 +107,22 @@ class ParametricFunction(VMobject, metaclass=ConvertToOpenGL):
97
107
 
98
108
  def __init__(
99
109
  self,
100
- function: Callable[[float, float], float],
101
- t_range: Sequence[float] | None = None,
110
+ function: Callable[[float], Point3DLike],
111
+ t_range: tuple[float, float] | tuple[float, float, float] = (0, 1),
102
112
  scaling: _ScaleBase = LinearBase(),
103
113
  dt: float = 1e-8,
104
114
  discontinuities: Iterable[float] | None = None,
105
115
  use_smoothing: bool = True,
106
116
  use_vectorized: bool = False,
107
- **kwargs,
117
+ **kwargs: Any,
108
118
  ):
109
- self.function = function
110
- t_range = [0, 1, 0.01] if t_range is None else t_range
119
+ def internal_parametric_function(t: float) -> Point3D:
120
+ """Wrap ``function``'s output inside a NumPy array."""
121
+ return np.asarray(function(t))
122
+
123
+ self.function = internal_parametric_function
111
124
  if len(t_range) == 2:
112
- t_range = np.array([*t_range, 0.01])
125
+ t_range = (*t_range, 0.01)
113
126
 
114
127
  self.scaling = scaling
115
128
 
@@ -121,26 +134,25 @@ class ParametricFunction(VMobject, metaclass=ConvertToOpenGL):
121
134
 
122
135
  super().__init__(**kwargs)
123
136
 
124
- def get_function(self):
137
+ def get_function(self) -> Callable[[float], Point3D]:
125
138
  return self.function
126
139
 
127
- def get_point_from_function(self, t):
140
+ def get_point_from_function(self, t: float) -> Point3D:
128
141
  return self.function(t)
129
142
 
130
- def generate_points(self):
131
-
143
+ def generate_points(self) -> Self:
132
144
  if self.discontinuities is not None:
133
145
  discontinuities = filter(
134
146
  lambda t: self.t_min <= t <= self.t_max,
135
147
  self.discontinuities,
136
148
  )
137
- discontinuities = np.array(list(discontinuities))
149
+ discontinuities_array = np.array(list(discontinuities))
138
150
  boundary_times = np.array(
139
151
  [
140
152
  self.t_min,
141
153
  self.t_max,
142
- *(discontinuities - self.dt),
143
- *(discontinuities + self.dt),
154
+ *(discontinuities_array - self.dt),
155
+ *(discontinuities_array + self.dt),
144
156
  ],
145
157
  )
146
158
  boundary_times.sort()
@@ -159,7 +171,7 @@ class ParametricFunction(VMobject, metaclass=ConvertToOpenGL):
159
171
  x, y, z = self.function(t_range)
160
172
  if not isinstance(z, np.ndarray):
161
173
  z = np.zeros_like(x)
162
- points = np.stack(zip(x, y, z), axis=0)
174
+ points = np.stack([x, y, z], axis=1)
163
175
  else:
164
176
  points = np.array([self.function(t) for t in t_range])
165
177
 
@@ -170,7 +182,8 @@ class ParametricFunction(VMobject, metaclass=ConvertToOpenGL):
170
182
  self.make_smooth()
171
183
  return self
172
184
 
173
- init_points = generate_points
185
+ def init_points(self) -> None:
186
+ self.generate_points()
174
187
 
175
188
 
176
189
  class FunctionGraph(ParametricFunction):
@@ -202,20 +215,27 @@ class FunctionGraph(ParametricFunction):
202
215
  self.add(cos_func, sin_func_1, sin_func_2)
203
216
  """
204
217
 
205
- def __init__(self, function, x_range=None, color=YELLOW, **kwargs):
206
-
218
+ def __init__(
219
+ self,
220
+ function: Callable[[float], Any],
221
+ x_range: tuple[float, float] | tuple[float, float, float] | None = None,
222
+ color: ParsableManimColor = YELLOW,
223
+ **kwargs: Any,
224
+ ) -> None:
207
225
  if x_range is None:
208
- x_range = np.array([-config["frame_x_radius"], config["frame_x_radius"]])
226
+ x_range = (-config["frame_x_radius"], config["frame_x_radius"])
209
227
 
210
228
  self.x_range = x_range
211
- self.parametric_function = lambda t: np.array([t, function(t), 0])
212
- self.function = function
229
+ self.parametric_function: Callable[[float], Point3D] = lambda t: np.array(
230
+ [t, function(t), 0]
231
+ )
232
+ self.function = function # type: ignore[assignment]
213
233
  super().__init__(self.parametric_function, self.x_range, color=color, **kwargs)
214
234
 
215
- def get_function(self):
235
+ def get_function(self) -> Callable[[float], Any]:
216
236
  return self.function
217
237
 
218
- def get_point_from_function(self, x):
238
+ def get_point_from_function(self, x: float) -> Point3D:
219
239
  return self.parametric_function(x)
220
240
 
221
241
 
@@ -228,7 +248,7 @@ class ImplicitFunction(VMobject, metaclass=ConvertToOpenGL):
228
248
  min_depth: int = 5,
229
249
  max_quads: int = 1500,
230
250
  use_smoothing: bool = True,
231
- **kwargs,
251
+ **kwargs: Any,
232
252
  ):
233
253
  """An implicit function.
234
254
 
@@ -287,7 +307,7 @@ class ImplicitFunction(VMobject, metaclass=ConvertToOpenGL):
287
307
 
288
308
  super().__init__(**kwargs)
289
309
 
290
- def generate_points(self):
310
+ def generate_points(self) -> Self:
291
311
  p_min, p_max = (
292
312
  np.array([self.x_range[0], self.y_range[0]]),
293
313
  np.array([self.x_range[1], self.y_range[1]]),
@@ -309,4 +329,5 @@ class ImplicitFunction(VMobject, metaclass=ConvertToOpenGL):
309
329
  self.make_smooth()
310
330
  return self
311
331
 
312
- init_points = generate_points
332
+ def init_points(self) -> None:
333
+ self.generate_points()
@@ -2,9 +2,22 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from manim.mobject.mobject import Mobject
6
+ from manim.mobject.opengl.opengl_vectorized_mobject import OpenGLVMobject
7
+
5
8
  __all__ = ["NumberLine", "UnitInterval"]
6
9
 
7
- from typing import Iterable, Sequence
10
+
11
+ from collections.abc import Callable, Iterable, Sequence
12
+ from typing import TYPE_CHECKING
13
+
14
+ if TYPE_CHECKING:
15
+ from typing import Any
16
+
17
+ from typing_extensions import Self
18
+
19
+ from manim.mobject.geometry.tips import ArrowTip
20
+ from manim.typing import Point3D, Point3DLike, Vector3D
8
21
 
9
22
  import numpy as np
10
23
 
@@ -12,8 +25,9 @@ from manim import config
12
25
  from manim.constants import *
13
26
  from manim.mobject.geometry.line import Line
14
27
  from manim.mobject.graphing.scale import LinearBase, _ScaleBase
15
- from manim.mobject.text.numbers import DecimalNumber
28
+ from manim.mobject.text.numbers import DecimalNumber, Integer
16
29
  from manim.mobject.text.tex_mobject import MathTex, Tex
30
+ from manim.mobject.text.text_mobject import Text
17
31
  from manim.mobject.types.vectorized_mobject import VGroup, VMobject
18
32
  from manim.utils.bezier import interpolate
19
33
  from manim.utils.config_ops import merge_dicts_recursively
@@ -49,6 +63,10 @@ class NumberLine(Line):
49
63
  The width of the tip.
50
64
  tip_height
51
65
  The height of the tip.
66
+ tip_shape
67
+ The mobject class used to construct the tip, or ``None`` (the
68
+ default) for the default arrow tip. Passed classes have to inherit
69
+ from :class:`.ArrowTip`.
52
70
  include_numbers
53
71
  Whether to add numbers to the tick marks. The number of decimal places is determined
54
72
  by the step size, this default can be overridden by ``decimal_number_config``.
@@ -140,17 +158,18 @@ class NumberLine(Line):
140
158
  include_tip: bool = False,
141
159
  tip_width: float = DEFAULT_ARROW_TIP_LENGTH,
142
160
  tip_height: float = DEFAULT_ARROW_TIP_LENGTH,
161
+ tip_shape: type[ArrowTip] | None = None,
143
162
  # numbers/labels
144
163
  include_numbers: bool = False,
145
164
  font_size: float = 36,
146
- label_direction: Sequence[float] = DOWN,
147
- label_constructor: VMobject = MathTex,
165
+ label_direction: Point3DLike = DOWN,
166
+ label_constructor: type[MathTex] = MathTex,
148
167
  scaling: _ScaleBase = LinearBase(),
149
168
  line_to_number_buff: float = MED_SMALL_BUFF,
150
169
  decimal_number_config: dict | None = None,
151
170
  numbers_to_exclude: Iterable[float] | None = None,
152
171
  numbers_to_include: Iterable[float] | None = None,
153
- **kwargs,
172
+ **kwargs: Any,
154
173
  ):
155
174
  # avoid mutable arguments in defaults
156
175
  if numbers_to_exclude is None:
@@ -173,8 +192,11 @@ class NumberLine(Line):
173
192
  "num_decimal_places": self._decimal_places_from_step(x_range[2]),
174
193
  }
175
194
 
176
- # turn into into an np array to scale by just applying the function
195
+ # turn into a NumPy array to scale by just applying the function
177
196
  self.x_range = np.array(x_range, dtype=float)
197
+ self.x_min: float
198
+ self.x_max: float
199
+ self.x_step: float
178
200
  self.x_min, self.x_max, self.x_step = scaling.function(self.x_range)
179
201
  self.length = length
180
202
  self.unit_size = unit_size
@@ -217,7 +239,11 @@ class NumberLine(Line):
217
239
  self.center()
218
240
 
219
241
  if self.include_tip:
220
- self.add_tip(tip_length=self.tip_height, tip_width=self.tip_width)
242
+ self.add_tip(
243
+ tip_length=self.tip_height,
244
+ tip_width=self.tip_width,
245
+ tip_shape=tip_shape,
246
+ )
221
247
  self.tip.set_stroke(self.stroke_color, self.stroke_width)
222
248
 
223
249
  if self.include_ticks:
@@ -228,16 +254,16 @@ class NumberLine(Line):
228
254
  if self.scaling.custom_labels:
229
255
  tick_range = self.get_tick_range()
230
256
 
257
+ custom_labels = self.scaling.get_custom_labels(
258
+ tick_range,
259
+ unit_decimal_places=decimal_number_config["num_decimal_places"],
260
+ )
261
+
231
262
  self.add_labels(
232
263
  dict(
233
264
  zip(
234
265
  tick_range,
235
- self.scaling.get_custom_labels(
236
- tick_range,
237
- unit_decimal_places=decimal_number_config[
238
- "num_decimal_places"
239
- ],
240
- ),
266
+ custom_labels,
241
267
  )
242
268
  ),
243
269
  )
@@ -249,22 +275,28 @@ class NumberLine(Line):
249
275
  font_size=self.font_size,
250
276
  )
251
277
 
252
- def rotate_about_zero(self, angle: float, axis: Sequence[float] = OUT, **kwargs):
278
+ def rotate_about_zero(
279
+ self, angle: float, axis: Vector3D = OUT, **kwargs: Any
280
+ ) -> Self:
253
281
  return self.rotate_about_number(0, angle, axis, **kwargs)
254
282
 
255
283
  def rotate_about_number(
256
- self, number: float, angle: float, axis: Sequence[float] = OUT, **kwargs
257
- ):
284
+ self, number: float, angle: float, axis: Vector3D = OUT, **kwargs: Any
285
+ ) -> Self:
258
286
  return self.rotate(angle, axis, about_point=self.n2p(number), **kwargs)
259
287
 
260
- def add_ticks(self):
288
+ def add_ticks(self) -> None:
261
289
  """Adds ticks to the number line. Ticks can be accessed after creation
262
- via ``self.ticks``."""
290
+ via ``self.ticks``.
291
+ """
263
292
  ticks = VGroup()
264
293
  elongated_tick_size = self.tick_size * self.longer_tick_multiple
294
+ elongated_tick_offsets = (
295
+ np.array(self.numbers_with_elongated_ticks) - self.x_min
296
+ )
265
297
  for x in self.get_tick_range():
266
298
  size = self.tick_size
267
- if x in self.numbers_with_elongated_ticks:
299
+ if np.any(np.isclose(x - self.x_min, elongated_tick_offsets)):
268
300
  size = elongated_tick_size
269
301
  ticks.add(self.get_tick(x, size))
270
302
  self.add(ticks)
@@ -327,6 +359,7 @@ class NumberLine(Line):
327
359
  def number_to_point(self, number: float | np.ndarray) -> np.ndarray:
328
360
  """Accepts a value along the number line and returns a point with
329
361
  respect to the scene.
362
+ Equivalent to `NumberLine @ number`
330
363
 
331
364
  Parameters
332
365
  ----------
@@ -347,7 +380,9 @@ class NumberLine(Line):
347
380
  array([0., 0., 0.])
348
381
  >>> number_line.number_to_point(1)
349
382
  array([1., 0., 0.])
350
- >>> number_line.number_to_point([1,2,3])
383
+ >>> number_line @ 1
384
+ array([1., 0., 0.])
385
+ >>> number_line.number_to_point([1, 2, 3])
351
386
  array([[1., 0., 0.],
352
387
  [2., 0., 0.],
353
388
  [3., 0., 0.]])
@@ -379,42 +414,45 @@ class NumberLine(Line):
379
414
 
380
415
  >>> from manim import NumberLine
381
416
  >>> number_line = NumberLine()
382
- >>> number_line.point_to_number((0,0,0))
383
- 0.0
384
- >>> number_line.point_to_number((1,0,0))
385
- 1.0
386
- >>> number_line.point_to_number([[0.5,0,0],[1,0,0],[1.5,0,0]])
417
+ >>> number_line.point_to_number((0, 0, 0))
418
+ np.float64(0.0)
419
+ >>> number_line.point_to_number((1, 0, 0))
420
+ np.float64(1.0)
421
+ >>> number_line.point_to_number([[0.5, 0, 0], [1, 0, 0], [1.5, 0, 0]])
387
422
  array([0.5, 1. , 1.5])
388
423
 
389
424
  """
390
425
  point = np.asarray(point)
391
426
  start, end = self.get_start_and_end()
392
427
  unit_vect = normalize(end - start)
393
- proportion = np.dot(point - start, unit_vect) / np.dot(end - start, unit_vect)
428
+ proportion: float = np.dot(point - start, unit_vect) / np.dot(
429
+ end - start, unit_vect
430
+ )
394
431
  return interpolate(self.x_min, self.x_max, proportion)
395
432
 
396
- def n2p(self, number: float | np.ndarray) -> np.ndarray:
433
+ def n2p(self, number: float | np.ndarray) -> Point3D:
397
434
  """Abbreviation for :meth:`~.NumberLine.number_to_point`."""
398
435
  return self.number_to_point(number)
399
436
 
400
- def p2n(self, point: Sequence[float]) -> float:
437
+ def p2n(self, point: Point3DLike) -> float:
401
438
  """Abbreviation for :meth:`~.NumberLine.point_to_number`."""
402
439
  return self.point_to_number(point)
403
440
 
404
441
  def get_unit_size(self) -> float:
405
- return self.get_length() / (self.x_range[1] - self.x_range[0])
442
+ val: float = self.get_length() / (self.x_range[1] - self.x_range[0])
443
+ return val
406
444
 
407
- def get_unit_vector(self) -> np.ndarray:
445
+ def get_unit_vector(self) -> Vector3D:
408
446
  return super().get_unit_vector() * self.unit_size
409
447
 
410
448
  def get_number_mobject(
411
449
  self,
412
450
  x: float,
413
- direction: Sequence[float] | None = None,
451
+ direction: Vector3D | None = None,
414
452
  buff: float | None = None,
415
453
  font_size: float | None = None,
416
- label_constructor: VMobject | None = None,
417
- **number_config,
454
+ label_constructor: type[MathTex] | None = None,
455
+ **number_config: dict[str, Any],
418
456
  ) -> VMobject:
419
457
  """Generates a positioned :class:`~.DecimalNumber` mobject
420
458
  generated according to ``label_constructor``.
@@ -439,7 +477,7 @@ class NumberLine(Line):
439
477
  :class:`~.DecimalNumber`
440
478
  The positioned mobject.
441
479
  """
442
- number_config = merge_dicts_recursively(
480
+ number_config_merged = merge_dicts_recursively(
443
481
  self.decimal_number_config,
444
482
  number_config,
445
483
  )
@@ -453,16 +491,19 @@ class NumberLine(Line):
453
491
  label_constructor = self.label_constructor
454
492
 
455
493
  num_mob = DecimalNumber(
456
- x, font_size=font_size, mob_class=label_constructor, **number_config
494
+ x,
495
+ font_size=font_size,
496
+ mob_class=label_constructor,
497
+ **number_config_merged,
457
498
  )
458
499
 
459
500
  num_mob.next_to(self.number_to_point(x), direction=direction, buff=buff)
460
501
  if x < 0 and self.label_direction[0] == 0:
461
502
  # Align without the minus sign
462
- num_mob.shift(num_mob[0].get_width() * LEFT / 2)
503
+ num_mob.shift(num_mob[0].width * LEFT / 2)
463
504
  return num_mob
464
505
 
465
- def get_number_mobjects(self, *numbers, **kwargs) -> VGroup:
506
+ def get_number_mobjects(self, *numbers: float, **kwargs: Any) -> VGroup:
466
507
  if len(numbers) == 0:
467
508
  numbers = self.default_numbers_to_display()
468
509
  return VGroup([self.get_number_mobject(number, **kwargs) for number in numbers])
@@ -475,9 +516,9 @@ class NumberLine(Line):
475
516
  x_values: Iterable[float] | None = None,
476
517
  excluding: Iterable[float] | None = None,
477
518
  font_size: float | None = None,
478
- label_constructor: VMobject | None = None,
479
- **kwargs,
480
- ):
519
+ label_constructor: type[MathTex] | None = None,
520
+ **kwargs: Any,
521
+ ) -> Self:
481
522
  """Adds :class:`~.DecimalNumber` mobjects representing their position
482
523
  at each tick of the number line. The numbers can be accessed after creation
483
524
  via ``self.numbers``.
@@ -528,11 +569,11 @@ class NumberLine(Line):
528
569
  def add_labels(
529
570
  self,
530
571
  dict_values: dict[float, str | float | VMobject],
531
- direction: Sequence[float] = None,
572
+ direction: Point3DLike | None = None,
532
573
  buff: float | None = None,
533
574
  font_size: float | None = None,
534
- label_constructor: VMobject | None = None,
535
- ):
575
+ label_constructor: type[MathTex] | None = None,
576
+ ) -> Self:
536
577
  """Adds specifically positioned labels to the :class:`~.NumberLine` using a ``dict``.
537
578
  The labels can be accessed after creation via ``self.labels``.
538
579
 
@@ -558,26 +599,24 @@ class NumberLine(Line):
558
599
  AttributeError
559
600
  If the label does not have a ``font_size`` attribute, an ``AttributeError`` is raised.
560
601
  """
561
-
562
602
  direction = self.label_direction if direction is None else direction
563
603
  buff = self.line_to_number_buff if buff is None else buff
564
604
  font_size = self.font_size if font_size is None else font_size
565
- label_constructor = (
566
- self.label_constructor if label_constructor is None else label_constructor
567
- )
605
+ if label_constructor is None:
606
+ label_constructor = self.label_constructor
568
607
 
569
608
  labels = VGroup()
570
609
  for x, label in dict_values.items():
571
-
572
610
  # TODO: remove this check and ability to call
573
611
  # this method via CoordinateSystem.add_coordinates()
574
612
  # must be explicitly called
575
- if isinstance(label, str) and self.label_constructor is MathTex:
613
+ if isinstance(label, str) and label_constructor is MathTex:
576
614
  label = Tex(label)
577
615
  else:
578
- label = self._create_label_tex(label)
616
+ label = self._create_label_tex(label, label_constructor)
579
617
 
580
618
  if hasattr(label, "font_size"):
619
+ assert isinstance(label, (MathTex, Tex, Text, Integer)), label
581
620
  label.font_size = font_size
582
621
  else:
583
622
  raise AttributeError(f"{label} is not compatible with add_labels.")
@@ -589,42 +628,61 @@ class NumberLine(Line):
589
628
  return self
590
629
 
591
630
  def _create_label_tex(
592
- self, label_tex: str | float | VMobject, **kwargs
631
+ self,
632
+ label_tex: str | float | VMobject,
633
+ label_constructor: Callable | None = None,
634
+ **kwargs: Any,
593
635
  ) -> VMobject:
594
636
  """Checks if the label is a :class:`~.VMobject`, otherwise, creates a
595
- label according to :attr:`label_constructor`.
637
+ label by passing ``label_tex`` to ``label_constructor``.
596
638
 
597
639
  Parameters
598
640
  ----------
599
641
  label_tex
600
- The label to be compared against the above types.
642
+ The label for which a mobject should be created. If the label already
643
+ is a mobject, no new mobject is created.
644
+ label_constructor
645
+ Optional. A class or function returning a mobject when
646
+ passing ``label_tex`` as an argument. If ``None`` is passed
647
+ (the default), the label constructor from the :attr:`.label_constructor`
648
+ attribute is used.
601
649
 
602
650
  Returns
603
651
  -------
604
652
  :class:`~.VMobject`
605
653
  The label.
606
654
  """
607
-
608
- if isinstance(label_tex, VMobject):
655
+ if isinstance(label_tex, (VMobject, OpenGLVMobject)):
609
656
  return label_tex
610
- else:
611
- return self.label_constructor(label_tex, **kwargs)
657
+ if label_constructor is None:
658
+ label_constructor = self.label_constructor
659
+ if isinstance(label_tex, str):
660
+ return label_constructor(label_tex, **kwargs)
661
+ return label_constructor(str(label_tex), **kwargs)
612
662
 
613
663
  @staticmethod
614
- def _decimal_places_from_step(step) -> int:
615
- step = str(step)
616
- if "." not in step:
664
+ def _decimal_places_from_step(step: float) -> int:
665
+ step_str = str(step)
666
+ if "." not in step_str:
617
667
  return 0
618
- return len(step.split(".")[-1])
668
+ return len(step_str.split(".")[-1])
669
+
670
+ def __matmul__(self, other: float) -> Point3D:
671
+ return self.n2p(other)
672
+
673
+ def __rmatmul__(self, other: Point3DLike | Mobject) -> float:
674
+ if isinstance(other, Mobject):
675
+ other = other.get_center()
676
+ return self.p2n(other)
619
677
 
620
678
 
621
679
  class UnitInterval(NumberLine):
622
680
  def __init__(
623
681
  self,
624
- unit_size=10,
625
- numbers_with_elongated_ticks=None,
626
- decimal_number_config=None,
627
- **kwargs,
682
+ unit_size: float = 10,
683
+ numbers_with_elongated_ticks: list[float] | None = None,
684
+ decimal_number_config: dict[str, Any] | None = None,
685
+ **kwargs: Any,
628
686
  ):
629
687
  numbers_with_elongated_ticks = (
630
688
  [0, 1]