manim 0.18.1__py3-none-any.whl → 0.19.0__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.

Potentially problematic release.


This version of manim might be problematic. Click here for more details.

Files changed (129) hide show
  1. manim/__main__.py +45 -12
  2. manim/_config/__init__.py +2 -2
  3. manim/_config/cli_colors.py +8 -4
  4. manim/_config/default.cfg +0 -2
  5. manim/_config/logger_utils.py +5 -0
  6. manim/_config/utils.py +29 -38
  7. manim/animation/animation.py +148 -8
  8. manim/animation/composition.py +16 -13
  9. manim/animation/creation.py +184 -8
  10. manim/animation/fading.py +5 -8
  11. manim/animation/indication.py +93 -26
  12. manim/animation/movement.py +21 -3
  13. manim/animation/rotation.py +2 -1
  14. manim/animation/specialized.py +3 -5
  15. manim/animation/speedmodifier.py +3 -3
  16. manim/animation/transform.py +4 -5
  17. manim/animation/updaters/mobject_update_utils.py +17 -14
  18. manim/camera/camera.py +2 -2
  19. manim/cli/__init__.py +17 -0
  20. manim/cli/cfg/group.py +52 -36
  21. manim/cli/checkhealth/checks.py +92 -76
  22. manim/cli/checkhealth/commands.py +12 -5
  23. manim/cli/default_group.py +148 -24
  24. manim/cli/init/commands.py +28 -23
  25. manim/cli/plugins/commands.py +13 -3
  26. manim/cli/render/commands.py +47 -42
  27. manim/cli/render/global_options.py +43 -9
  28. manim/cli/render/render_options.py +84 -19
  29. manim/constants.py +11 -4
  30. manim/mobject/frame.py +0 -1
  31. manim/mobject/geometry/arc.py +109 -75
  32. manim/mobject/geometry/boolean_ops.py +20 -17
  33. manim/mobject/geometry/labeled.py +300 -77
  34. manim/mobject/geometry/line.py +120 -60
  35. manim/mobject/geometry/polygram.py +109 -25
  36. manim/mobject/geometry/shape_matchers.py +35 -15
  37. manim/mobject/geometry/tips.py +36 -27
  38. manim/mobject/graph.py +48 -40
  39. manim/mobject/graphing/coordinate_systems.py +110 -45
  40. manim/mobject/graphing/functions.py +16 -10
  41. manim/mobject/graphing/number_line.py +23 -9
  42. manim/mobject/graphing/probability.py +2 -10
  43. manim/mobject/graphing/scale.py +6 -5
  44. manim/mobject/matrix.py +17 -19
  45. manim/mobject/mobject.py +149 -103
  46. manim/mobject/opengl/opengl_geometry.py +4 -8
  47. manim/mobject/opengl/opengl_mobject.py +506 -343
  48. manim/mobject/opengl/opengl_point_cloud_mobject.py +3 -7
  49. manim/mobject/opengl/opengl_surface.py +1 -2
  50. manim/mobject/opengl/opengl_vectorized_mobject.py +27 -65
  51. manim/mobject/svg/brace.py +61 -13
  52. manim/mobject/svg/svg_mobject.py +2 -1
  53. manim/mobject/table.py +11 -12
  54. manim/mobject/text/code_mobject.py +186 -550
  55. manim/mobject/text/numbers.py +7 -7
  56. manim/mobject/text/tex_mobject.py +22 -13
  57. manim/mobject/text/text_mobject.py +29 -20
  58. manim/mobject/three_d/polyhedra.py +98 -1
  59. manim/mobject/three_d/three_dimensions.py +59 -31
  60. manim/mobject/types/image_mobject.py +37 -23
  61. manim/mobject/types/point_cloud_mobject.py +103 -67
  62. manim/mobject/types/vectorized_mobject.py +387 -214
  63. manim/mobject/value_tracker.py +2 -1
  64. manim/mobject/vector_field.py +2 -4
  65. manim/opengl/__init__.py +3 -3
  66. manim/plugins/__init__.py +2 -3
  67. manim/plugins/plugins_flags.py +3 -3
  68. manim/renderer/cairo_renderer.py +11 -11
  69. manim/renderer/opengl_renderer.py +19 -20
  70. manim/renderer/shader.py +2 -3
  71. manim/renderer/shader_wrapper.py +3 -2
  72. manim/scene/moving_camera_scene.py +23 -0
  73. manim/scene/scene.py +72 -41
  74. manim/scene/scene_file_writer.py +313 -164
  75. manim/scene/section.py +15 -15
  76. manim/scene/three_d_scene.py +8 -15
  77. manim/scene/vector_space_scene.py +3 -6
  78. manim/typing.py +326 -66
  79. manim/utils/bezier.py +1658 -381
  80. manim/utils/caching.py +11 -5
  81. manim/utils/color/AS2700.py +2 -0
  82. manim/utils/color/BS381.py +2 -0
  83. manim/utils/color/DVIPSNAMES.py +96 -0
  84. manim/utils/color/SVGNAMES.py +179 -0
  85. manim/utils/color/X11.py +3 -0
  86. manim/utils/color/XKCD.py +2 -0
  87. manim/utils/color/__init__.py +8 -5
  88. manim/utils/color/core.py +818 -301
  89. manim/utils/color/manim_colors.py +7 -9
  90. manim/utils/commands.py +40 -19
  91. manim/utils/config_ops.py +18 -13
  92. manim/utils/debug.py +8 -6
  93. manim/utils/deprecation.py +92 -43
  94. manim/utils/docbuild/autoaliasattr_directive.py +45 -8
  95. manim/utils/docbuild/autocolor_directive.py +12 -13
  96. manim/utils/docbuild/manim_directive.py +35 -29
  97. manim/utils/docbuild/module_parsing.py +74 -27
  98. manim/utils/family.py +3 -3
  99. manim/utils/family_ops.py +12 -4
  100. manim/utils/file_ops.py +22 -16
  101. manim/utils/hashing.py +7 -7
  102. manim/utils/images.py +10 -4
  103. manim/utils/ipython_magic.py +12 -8
  104. manim/utils/iterables.py +161 -119
  105. manim/utils/module_ops.py +55 -19
  106. manim/utils/opengl.py +68 -23
  107. manim/utils/parameter_parsing.py +3 -2
  108. manim/utils/paths.py +11 -5
  109. manim/utils/polylabel.py +168 -0
  110. manim/utils/qhull.py +218 -0
  111. manim/utils/rate_functions.py +69 -32
  112. manim/utils/simple_functions.py +24 -15
  113. manim/utils/sounds.py +7 -1
  114. manim/utils/space_ops.py +48 -37
  115. manim/utils/testing/_frames_testers.py +13 -8
  116. manim/utils/testing/_show_diff.py +5 -3
  117. manim/utils/testing/_test_class_makers.py +33 -18
  118. manim/utils/testing/frames_comparison.py +20 -14
  119. manim/utils/tex.py +4 -2
  120. manim/utils/tex_file_writing.py +45 -45
  121. manim/utils/tex_templates.py +1 -1
  122. manim/utils/unit.py +6 -5
  123. {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/METADATA +16 -9
  124. manim-0.19.0.dist-info/RECORD +221 -0
  125. {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/WHEEL +1 -1
  126. manim-0.18.1.dist-info/RECORD +0 -217
  127. {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/LICENSE +0 -0
  128. {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/LICENSE.community +0 -0
  129. {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/entry_points.txt +0 -0
@@ -70,17 +70,22 @@ __all__ = [
70
70
  "RemoveTextLetterByLetter",
71
71
  "ShowSubmobjectsOneByOne",
72
72
  "AddTextWordByWord",
73
+ "TypeWithCursor",
74
+ "UntypeWithCursor",
73
75
  ]
74
76
 
75
77
 
76
78
  import itertools as it
77
- from typing import TYPE_CHECKING, Callable, Iterable, Sequence
79
+ from collections.abc import Iterable, Sequence
80
+ from typing import TYPE_CHECKING, Callable
78
81
 
79
82
  import numpy as np
80
83
 
81
84
  if TYPE_CHECKING:
82
85
  from manim.mobject.text.text_mobject import Text
86
+ from manim.scene.scene import Scene
83
87
 
88
+ from manim.constants import RIGHT, TAU
84
89
  from manim.mobject.opengl.opengl_surface import OpenGLSurface
85
90
  from manim.mobject.opengl.opengl_vectorized_mobject import OpenGLVMobject
86
91
  from manim.utils.color import ManimColor
@@ -88,7 +93,6 @@ from manim.utils.color import ManimColor
88
93
  from .. import config
89
94
  from ..animation.animation import Animation
90
95
  from ..animation.composition import Succession
91
- from ..constants import TAU
92
96
  from ..mobject.mobject import Group, Mobject
93
97
  from ..mobject.types.vectorized_mobject import VMobject
94
98
  from ..utils.bezier import integer_interpolate
@@ -246,7 +250,9 @@ class DrawBorderThenFill(Animation):
246
250
 
247
251
  def _typecheck_input(self, vmobject: VMobject | OpenGLVMobject) -> None:
248
252
  if not isinstance(vmobject, (VMobject, OpenGLVMobject)):
249
- raise TypeError("DrawBorderThenFill only works for vectorized Mobjects")
253
+ raise TypeError(
254
+ f"{self.__class__.__name__} only works for vectorized Mobjects"
255
+ )
250
256
 
251
257
  def begin(self) -> None:
252
258
  self.outline = self.get_outline()
@@ -277,7 +283,7 @@ class DrawBorderThenFill(Animation):
277
283
  alpha: float,
278
284
  ) -> None: # Fixme: not matching the parent class? What is outline doing here?
279
285
  index: int
280
- subalpha: int
286
+ subalpha: float
281
287
  index, subalpha = integer_interpolate(0, 2, alpha)
282
288
  if index == 0:
283
289
  submobject.pointwise_become_partial(outline, 0, subalpha)
@@ -347,10 +353,7 @@ class Write(DrawBorderThenFill):
347
353
  ) -> tuple[float, float]:
348
354
  length = len(vmobject.family_members_with_points())
349
355
  if run_time is None:
350
- if length < 15:
351
- run_time = 1
352
- else:
353
- run_time = 2
356
+ run_time = 1 if length < 15 else 2
354
357
  if lag_ratio is None:
355
358
  lag_ratio = min(4.0 / max(1.0, length), 0.2)
356
359
  return run_time, lag_ratio
@@ -674,3 +677,176 @@ class AddTextWordByWord(Succession):
674
677
  )
675
678
  )
676
679
  super().__init__(*anims, **kwargs)
680
+
681
+
682
+ class TypeWithCursor(AddTextLetterByLetter):
683
+ """Similar to :class:`~.AddTextLetterByLetter` , but with an additional cursor mobject at the end.
684
+
685
+ Parameters
686
+ ----------
687
+ time_per_char
688
+ Frequency of appearance of the letters.
689
+ cursor
690
+ :class:`~.Mobject` shown after the last added letter.
691
+ buff
692
+ Controls how far away the cursor is to the right of the last added letter.
693
+ keep_cursor_y
694
+ If ``True``, the cursor's y-coordinate is set to the center of the ``Text`` and remains the same throughout the animation. Otherwise, it is set to the center of the last added letter.
695
+ leave_cursor_on
696
+ Whether to show the cursor after the animation.
697
+
698
+ .. tip::
699
+ This is currently only possible for class:`~.Text` and not for class:`~.MathTex`.
700
+
701
+
702
+ Examples
703
+ --------
704
+
705
+ .. manim:: InsertingTextExample
706
+ :ref_classes: Blink
707
+
708
+ class InsertingTextExample(Scene):
709
+ def construct(self):
710
+ text = Text("Inserting", color=PURPLE).scale(1.5).to_edge(LEFT)
711
+ cursor = Rectangle(
712
+ color = GREY_A,
713
+ fill_color = GREY_A,
714
+ fill_opacity = 1.0,
715
+ height = 1.1,
716
+ width = 0.5,
717
+ ).move_to(text[0]) # Position the cursor
718
+
719
+ self.play(TypeWithCursor(text, cursor))
720
+ self.play(Blink(cursor, blinks=2))
721
+
722
+ """
723
+
724
+ def __init__(
725
+ self,
726
+ text: Text,
727
+ cursor: Mobject,
728
+ buff: float = 0.1,
729
+ keep_cursor_y: bool = True,
730
+ leave_cursor_on: bool = True,
731
+ time_per_char: float = 0.1,
732
+ reverse_rate_function=False,
733
+ introducer=True,
734
+ **kwargs,
735
+ ) -> None:
736
+ self.cursor = cursor
737
+ self.buff = buff
738
+ self.keep_cursor_y = keep_cursor_y
739
+ self.leave_cursor_on = leave_cursor_on
740
+ super().__init__(
741
+ text,
742
+ time_per_char=time_per_char,
743
+ reverse_rate_function=reverse_rate_function,
744
+ introducer=introducer,
745
+ **kwargs,
746
+ )
747
+
748
+ def begin(self) -> None:
749
+ self.y_cursor = self.cursor.get_y()
750
+ self.cursor.initial_position = self.mobject.get_center()
751
+ if self.keep_cursor_y:
752
+ self.cursor.set_y(self.y_cursor)
753
+
754
+ self.cursor.set_opacity(0)
755
+ self.mobject.add(self.cursor)
756
+ super().begin()
757
+
758
+ def finish(self) -> None:
759
+ if self.leave_cursor_on:
760
+ self.cursor.set_opacity(1)
761
+ else:
762
+ self.cursor.set_opacity(0)
763
+ self.mobject.remove(self.cursor)
764
+ super().finish()
765
+
766
+ def clean_up_from_scene(self, scene: Scene) -> None:
767
+ if not self.leave_cursor_on:
768
+ scene.remove(self.cursor)
769
+ super().clean_up_from_scene(scene)
770
+
771
+ def update_submobject_list(self, index: int) -> None:
772
+ for mobj in self.all_submobs[:index]:
773
+ mobj.set_opacity(1)
774
+
775
+ for mobj in self.all_submobs[index:]:
776
+ mobj.set_opacity(0)
777
+
778
+ if index != 0:
779
+ self.cursor.next_to(
780
+ self.all_submobs[index - 1], RIGHT, buff=self.buff
781
+ ).set_y(self.cursor.initial_position[1])
782
+ else:
783
+ self.cursor.move_to(self.all_submobs[0]).set_y(
784
+ self.cursor.initial_position[1]
785
+ )
786
+
787
+ if self.keep_cursor_y:
788
+ self.cursor.set_y(self.y_cursor)
789
+ self.cursor.set_opacity(1)
790
+
791
+
792
+ class UntypeWithCursor(TypeWithCursor):
793
+ """Similar to :class:`~.RemoveTextLetterByLetter` , but with an additional cursor mobject at the end.
794
+
795
+ Parameters
796
+ ----------
797
+ time_per_char
798
+ Frequency of appearance of the letters.
799
+ cursor
800
+ :class:`~.Mobject` shown after the last added letter.
801
+ buff
802
+ Controls how far away the cursor is to the right of the last added letter.
803
+ keep_cursor_y
804
+ If ``True``, the cursor's y-coordinate is set to the center of the ``Text`` and remains the same throughout the animation. Otherwise, it is set to the center of the last added letter.
805
+ leave_cursor_on
806
+ Whether to show the cursor after the animation.
807
+
808
+ .. tip::
809
+ This is currently only possible for class:`~.Text` and not for class:`~.MathTex`.
810
+
811
+
812
+ Examples
813
+ --------
814
+
815
+ .. manim:: DeletingTextExample
816
+ :ref_classes: Blink
817
+
818
+ class DeletingTextExample(Scene):
819
+ def construct(self):
820
+ text = Text("Deleting", color=PURPLE).scale(1.5).to_edge(LEFT)
821
+ cursor = Rectangle(
822
+ color = GREY_A,
823
+ fill_color = GREY_A,
824
+ fill_opacity = 1.0,
825
+ height = 1.1,
826
+ width = 0.5,
827
+ ).move_to(text[0]) # Position the cursor
828
+
829
+ self.play(UntypeWithCursor(text, cursor))
830
+ self.play(Blink(cursor, blinks=2))
831
+
832
+ """
833
+
834
+ def __init__(
835
+ self,
836
+ text: Text,
837
+ cursor: VMobject | None = None,
838
+ time_per_char: float = 0.1,
839
+ reverse_rate_function=True,
840
+ introducer=False,
841
+ remover=True,
842
+ **kwargs,
843
+ ) -> None:
844
+ super().__init__(
845
+ text,
846
+ cursor=cursor,
847
+ time_per_char=time_per_char,
848
+ reverse_rate_function=reverse_rate_function,
849
+ introducer=introducer,
850
+ remover=remover,
851
+ **kwargs,
852
+ )
manim/animation/fading.py CHANGED
@@ -57,10 +57,7 @@ class _Fade(Transform):
57
57
  ) -> None:
58
58
  if not mobjects:
59
59
  raise ValueError("At least one mobject must be passed.")
60
- if len(mobjects) == 1:
61
- mobject = mobjects[0]
62
- else:
63
- mobject = Group(*mobjects)
60
+ mobject = mobjects[0] if len(mobjects) == 1 else Group(*mobjects)
64
61
 
65
62
  self.point_target = False
66
63
  if shift is None:
@@ -97,7 +94,7 @@ class _Fade(Transform):
97
94
 
98
95
 
99
96
  class FadeIn(_Fade):
100
- """Fade in :class:`~.Mobject` s.
97
+ r"""Fade in :class:`~.Mobject` s.
101
98
 
102
99
  Parameters
103
100
  ----------
@@ -122,7 +119,7 @@ class FadeIn(_Fade):
122
119
  dot = Dot(UP * 2 + LEFT)
123
120
  self.add(dot)
124
121
  tex = Tex(
125
- "FadeIn with ", "shift ", " or target\\_position", " and scale"
122
+ "FadeIn with ", "shift ", r" or target\_position", " and scale"
126
123
  ).scale(1)
127
124
  animations = [
128
125
  FadeIn(tex[0]),
@@ -145,7 +142,7 @@ class FadeIn(_Fade):
145
142
 
146
143
 
147
144
  class FadeOut(_Fade):
148
- """Fade out :class:`~.Mobject` s.
145
+ r"""Fade out :class:`~.Mobject` s.
149
146
 
150
147
  Parameters
151
148
  ----------
@@ -169,7 +166,7 @@ class FadeOut(_Fade):
169
166
  dot = Dot(UP * 2 + LEFT)
170
167
  self.add(dot)
171
168
  tex = Tex(
172
- "FadeOut with ", "shift ", " or target\\_position", " and scale"
169
+ "FadeOut with ", "shift ", r" or target\_position", " and scale"
173
170
  ).scale(1)
174
171
  animations = [
175
172
  FadeOut(tex[0]),
@@ -25,6 +25,8 @@ Examples
25
25
 
26
26
  """
27
27
 
28
+ from __future__ import annotations
29
+
28
30
  __all__ = [
29
31
  "FocusOn",
30
32
  "Indicate",
@@ -34,9 +36,11 @@ __all__ = [
34
36
  "ApplyWave",
35
37
  "Circumscribe",
36
38
  "Wiggle",
39
+ "Blink",
37
40
  ]
38
41
 
39
- from typing import Callable, Iterable, Optional, Tuple, Type, Union
42
+ from collections.abc import Iterable
43
+ from typing import Callable
40
44
 
41
45
  import numpy as np
42
46
 
@@ -53,12 +57,12 @@ from ..animation.creation import Create, ShowPartial, Uncreate
53
57
  from ..animation.fading import FadeIn, FadeOut
54
58
  from ..animation.movement import Homotopy
55
59
  from ..animation.transform import Transform
60
+ from ..animation.updaters.update import UpdateFromFunc
56
61
  from ..constants import *
57
62
  from ..mobject.mobject import Mobject
58
63
  from ..mobject.types.vectorized_mobject import VGroup, VMobject
59
64
  from ..utils.bezier import interpolate, inverse_interpolate
60
65
  from ..utils.color import GREY, YELLOW, ParsableManimColor
61
- from ..utils.deprecation import deprecated
62
66
  from ..utils.rate_functions import smooth, there_and_back, wiggle
63
67
  from ..utils.space_ops import normalize
64
68
 
@@ -76,8 +80,6 @@ class FocusOn(Transform):
76
80
  The color of the spotlight.
77
81
  run_time
78
82
  The duration of the animation.
79
- kwargs
80
- Additional arguments to be passed to the :class:`~.Succession` constructor
81
83
 
82
84
  Examples
83
85
  --------
@@ -93,11 +95,11 @@ class FocusOn(Transform):
93
95
 
94
96
  def __init__(
95
97
  self,
96
- focus_point: Union[np.ndarray, Mobject],
98
+ focus_point: np.ndarray | Mobject,
97
99
  opacity: float = 0.2,
98
100
  color: str = GREY,
99
101
  run_time: float = 2,
100
- **kwargs
102
+ **kwargs,
101
103
  ) -> None:
102
104
  self.focus_point = focus_point
103
105
  self.color = color
@@ -150,8 +152,8 @@ class Indicate(Transform):
150
152
  mobject: Mobject,
151
153
  scale_factor: float = 1.2,
152
154
  color: str = YELLOW,
153
- rate_func: Callable[[float, Optional[float]], np.ndarray] = there_and_back,
154
- **kwargs
155
+ rate_func: Callable[[float, float | None], np.ndarray] = there_and_back,
156
+ **kwargs,
155
157
  ) -> None:
156
158
  self.color = color
157
159
  self.scale_factor = scale_factor
@@ -217,7 +219,7 @@ class Flash(AnimationGroup):
217
219
 
218
220
  def __init__(
219
221
  self,
220
- point: Union[np.ndarray, Mobject],
222
+ point: np.ndarray | Mobject,
221
223
  line_length: float = 0.2,
222
224
  num_lines: int = 12,
223
225
  flash_radius: float = 0.1,
@@ -225,7 +227,7 @@ class Flash(AnimationGroup):
225
227
  color: str = YELLOW,
226
228
  time_width: float = 1,
227
229
  run_time: float = 1.0,
228
- **kwargs
230
+ **kwargs,
229
231
  ) -> None:
230
232
  if isinstance(point, Mobject):
231
233
  self.point = point.get_center()
@@ -255,7 +257,7 @@ class Flash(AnimationGroup):
255
257
  lines.set_stroke(width=self.line_stroke_width)
256
258
  return lines
257
259
 
258
- def create_line_anims(self) -> Iterable["ShowPassingFlash"]:
260
+ def create_line_anims(self) -> Iterable[ShowPassingFlash]:
259
261
  return [
260
262
  ShowPassingFlash(
261
263
  line,
@@ -268,7 +270,7 @@ class Flash(AnimationGroup):
268
270
 
269
271
 
270
272
  class ShowPassingFlash(ShowPartial):
271
- """Show only a sliver of the VMobject each frame.
273
+ r"""Show only a sliver of the VMobject each frame.
272
274
 
273
275
  Parameters
274
276
  ----------
@@ -288,7 +290,7 @@ class ShowPassingFlash(ShowPartial):
288
290
  self.add(p, lbl)
289
291
  p = p.copy().set_color(BLUE)
290
292
  for time_width in [0.2, 0.5, 1, 2]:
291
- lbl.become(Tex(r"\\texttt{time\\_width={{%.1f}}}"%time_width))
293
+ lbl.become(Tex(r"\texttt{time\_width={{%.1f}}}"%time_width))
292
294
  self.play(ShowPassingFlash(
293
295
  p.copy().set_color(BLUE),
294
296
  run_time=2,
@@ -301,11 +303,11 @@ class ShowPassingFlash(ShowPartial):
301
303
 
302
304
  """
303
305
 
304
- def __init__(self, mobject: "VMobject", time_width: float = 0.1, **kwargs) -> None:
306
+ def __init__(self, mobject: VMobject, time_width: float = 0.1, **kwargs) -> None:
305
307
  self.time_width = time_width
306
308
  super().__init__(mobject, remover=True, introducer=True, **kwargs)
307
309
 
308
- def _get_bounds(self, alpha: float) -> Tuple[float]:
310
+ def _get_bounds(self, alpha: float) -> tuple[float]:
309
311
  tw = self.time_width
310
312
  upper = interpolate(0, 1 + tw, alpha)
311
313
  lower = upper - tw
@@ -393,7 +395,7 @@ class ApplyWave(Homotopy):
393
395
  time_width: float = 1,
394
396
  ripples: int = 1,
395
397
  run_time: float = 2,
396
- **kwargs
398
+ **kwargs,
397
399
  ) -> None:
398
400
  x_min = mobject.get_left()[0]
399
401
  x_max = mobject.get_right()[0]
@@ -459,7 +461,7 @@ class ApplyWave(Homotopy):
459
461
  y: float,
460
462
  z: float,
461
463
  t: float,
462
- ) -> Tuple[float, float, float]:
464
+ ) -> tuple[float, float, float]:
463
465
  upper = interpolate(0, 1 + time_width, t)
464
466
  lower = upper - time_width
465
467
  relative_x = inverse_interpolate(x_min, x_max, x)
@@ -509,10 +511,10 @@ class Wiggle(Animation):
509
511
  scale_value: float = 1.1,
510
512
  rotation_angle: float = 0.01 * TAU,
511
513
  n_wiggles: int = 6,
512
- scale_about_point: Optional[np.ndarray] = None,
513
- rotate_about_point: Optional[np.ndarray] = None,
514
+ scale_about_point: np.ndarray | None = None,
515
+ rotate_about_point: np.ndarray | None = None,
514
516
  run_time: float = 2,
515
- **kwargs
517
+ **kwargs,
516
518
  ) -> None:
517
519
  self.scale_value = scale_value
518
520
  self.rotation_angle = rotation_angle
@@ -549,7 +551,7 @@ class Wiggle(Animation):
549
551
 
550
552
 
551
553
  class Circumscribe(Succession):
552
- """Draw a temporary line surrounding the mobject.
554
+ r"""Draw a temporary line surrounding the mobject.
553
555
 
554
556
  Parameters
555
557
  ----------
@@ -580,7 +582,7 @@ class Circumscribe(Succession):
580
582
 
581
583
  class UsingCircumscribe(Scene):
582
584
  def construct(self):
583
- lbl = Tex(r"Circum-\\\\scribe").scale(2)
585
+ lbl = Tex(r"Circum-\\scribe").scale(2)
584
586
  self.add(lbl)
585
587
  self.play(Circumscribe(lbl))
586
588
  self.play(Circumscribe(lbl, Circle))
@@ -593,7 +595,7 @@ class Circumscribe(Succession):
593
595
  def __init__(
594
596
  self,
595
597
  mobject: Mobject,
596
- shape: Type = Rectangle,
598
+ shape: type = Rectangle,
597
599
  fade_in=False,
598
600
  fade_out=False,
599
601
  time_width=0.3,
@@ -601,13 +603,13 @@ class Circumscribe(Succession):
601
603
  color: ParsableManimColor = YELLOW,
602
604
  run_time=1,
603
605
  stroke_width=DEFAULT_STROKE_WIDTH,
604
- **kwargs
606
+ **kwargs,
605
607
  ):
606
608
  if shape is Rectangle:
607
609
  frame = SurroundingRectangle(
608
610
  mobject,
609
- color,
610
- buff,
611
+ color=color,
612
+ buff=buff,
611
613
  stroke_width=stroke_width,
612
614
  )
613
615
  elif shape is Circle:
@@ -643,3 +645,68 @@ class Circumscribe(Succession):
643
645
  super().__init__(
644
646
  ShowPassingFlash(frame, time_width, run_time=run_time), **kwargs
645
647
  )
648
+
649
+
650
+ class Blink(Succession):
651
+ """Blink the mobject.
652
+
653
+ Parameters
654
+ ----------
655
+ mobject
656
+ The mobject to be blinked.
657
+ time_on
658
+ The duration that the mobject is shown for one blink.
659
+ time_off
660
+ The duration that the mobject is hidden for one blink.
661
+ blinks
662
+ The number of blinks
663
+ hide_at_end
664
+ Whether to hide the mobject at the end of the animation.
665
+ kwargs
666
+ Additional arguments to be passed to the :class:`~.Succession` constructor.
667
+
668
+ Examples
669
+ --------
670
+
671
+ .. manim:: BlinkingExample
672
+
673
+ class BlinkingExample(Scene):
674
+ def construct(self):
675
+ text = Text("Blinking").scale(1.5)
676
+ self.add(text)
677
+ self.play(Blink(text, blinks=3))
678
+
679
+ """
680
+
681
+ def __init__(
682
+ self,
683
+ mobject: Mobject,
684
+ time_on: float = 0.5,
685
+ time_off: float = 0.5,
686
+ blinks: int = 1,
687
+ hide_at_end: bool = False,
688
+ **kwargs,
689
+ ):
690
+ animations = [
691
+ UpdateFromFunc(
692
+ mobject,
693
+ update_function=lambda mob: mob.set_opacity(1.0),
694
+ run_time=time_on,
695
+ ),
696
+ UpdateFromFunc(
697
+ mobject,
698
+ update_function=lambda mob: mob.set_opacity(0.0),
699
+ run_time=time_off,
700
+ ),
701
+ ] * blinks
702
+
703
+ if not hide_at_end:
704
+ animations.append(
705
+ UpdateFromFunc(
706
+ mobject,
707
+ update_function=lambda mob: mob.set_opacity(1.0),
708
+ run_time=time_on,
709
+ ),
710
+ )
711
+
712
+ super().__init__(*animations, **kwargs)
@@ -44,6 +44,26 @@ class Homotopy(Animation):
44
44
  Keyword arguments propagated to :meth:`.Mobject.apply_function`.
45
45
  kwargs
46
46
  Further keyword arguments passed to the parent class.
47
+
48
+ Examples
49
+ --------
50
+
51
+ .. manim:: HomotopyExample
52
+
53
+ class HomotopyExample(Scene):
54
+ def construct(self):
55
+ square = Square()
56
+
57
+ def homotopy(x, y, z, t):
58
+ if t <= 0.25:
59
+ progress = t / 0.25
60
+ return (x, y + progress * 0.2 * np.sin(x), z)
61
+ else:
62
+ wave_progress = (t - 0.25) / 0.75
63
+ return (x, y + 0.2 * np.sin(x + 10 * wave_progress), z)
64
+
65
+ self.play(Homotopy(homotopy, square, rate_func= linear, run_time=2))
66
+
47
67
  """
48
68
 
49
69
  def __init__(
@@ -90,9 +110,7 @@ class ComplexHomotopy(Homotopy):
90
110
  def __init__(
91
111
  self, complex_homotopy: Callable[[complex], float], mobject: Mobject, **kwargs
92
112
  ) -> None:
93
- """
94
- Complex Homotopy a function Cx[0, 1] to C
95
- """
113
+ """Complex Homotopy a function Cx[0, 1] to C"""
96
114
 
97
115
  def homotopy(
98
116
  x: float,
@@ -4,7 +4,8 @@ from __future__ import annotations
4
4
 
5
5
  __all__ = ["Rotating", "Rotate"]
6
6
 
7
- from typing import TYPE_CHECKING, Callable, Sequence
7
+ from collections.abc import Sequence
8
+ from typing import TYPE_CHECKING, Callable
8
9
 
9
10
  import numpy as np
10
11
 
@@ -2,7 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  __all__ = ["Broadcast"]
4
4
 
5
- from typing import Any, Sequence
5
+ from collections.abc import Sequence
6
+ from typing import Any
6
7
 
7
8
  from manim.animation.transform import Restore
8
9
 
@@ -69,10 +70,7 @@ class Broadcast(LaggedStart):
69
70
  anims = []
70
71
 
71
72
  # Works by saving the mob that is passed into the animation, scaling it to 0 (or the initial_width) and then restoring the original mob.
72
- if mobject.fill_opacity:
73
- fill_o = True
74
- else:
75
- fill_o = False
73
+ fill_o = bool(mobject.fill_opacity)
76
74
 
77
75
  for _ in range(self.n_mobs):
78
76
  mob = mobject.copy()
@@ -113,9 +113,9 @@ class ChangeSpeed(Animation):
113
113
  self.anim = self.setup(anim)
114
114
 
115
115
  if affects_speed_updaters:
116
- assert (
117
- ChangeSpeed.is_changing_dt is False
118
- ), "Only one animation at a time can play that changes speed (dt) for ChangeSpeed updaters"
116
+ assert ChangeSpeed.is_changing_dt is False, (
117
+ "Only one animation at a time can play that changes speed (dt) for ChangeSpeed updaters"
118
+ )
119
119
  ChangeSpeed.is_changing_dt = True
120
120
  self.t = 0
121
121
  self.affects_speed_updaters = affects_speed_updaters
@@ -28,7 +28,8 @@ __all__ = [
28
28
 
29
29
  import inspect
30
30
  import types
31
- from typing import TYPE_CHECKING, Any, Callable, Iterable, Sequence
31
+ from collections.abc import Iterable, Sequence
32
+ from typing import TYPE_CHECKING, Any, Callable
32
33
 
33
34
  import numpy as np
34
35
 
@@ -297,9 +298,7 @@ class ReplacementTransform(Transform):
297
298
 
298
299
 
299
300
  class TransformFromCopy(Transform):
300
- """
301
- Performs a reversed Transform
302
- """
301
+ """Performs a reversed Transform"""
303
302
 
304
303
  def __init__(self, mobject: Mobject, target_mobject: Mobject, **kwargs) -> None:
305
304
  super().__init__(target_mobject, mobject, **kwargs)
@@ -430,7 +429,7 @@ class MoveToTarget(Transform):
430
429
  def check_validity_of_input(self, mobject: Mobject) -> None:
431
430
  if not hasattr(mobject, "target"):
432
431
  raise ValueError(
433
- "MoveToTarget called on mobject" "without attribute 'target'",
432
+ "MoveToTarget called on mobjectwithout attribute 'target'",
434
433
  )
435
434
 
436
435