manim 0.18.0.post0__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 (146) hide show
  1. manim/__init__.py +3 -6
  2. manim/__main__.py +61 -20
  3. manim/_config/__init__.py +6 -3
  4. manim/_config/cli_colors.py +16 -8
  5. manim/_config/default.cfg +1 -3
  6. manim/_config/logger_utils.py +14 -8
  7. manim/_config/utils.py +651 -472
  8. manim/animation/animation.py +152 -5
  9. manim/animation/composition.py +80 -39
  10. manim/animation/creation.py +196 -14
  11. manim/animation/fading.py +5 -9
  12. manim/animation/indication.py +103 -47
  13. manim/animation/movement.py +22 -5
  14. manim/animation/rotation.py +3 -2
  15. manim/animation/specialized.py +4 -6
  16. manim/animation/speedmodifier.py +10 -5
  17. manim/animation/transform.py +4 -5
  18. manim/animation/transform_matching_parts.py +1 -1
  19. manim/animation/updaters/mobject_update_utils.py +17 -14
  20. manim/camera/camera.py +15 -6
  21. manim/cli/__init__.py +17 -0
  22. manim/cli/cfg/group.py +70 -44
  23. manim/cli/checkhealth/checks.py +93 -75
  24. manim/cli/checkhealth/commands.py +14 -5
  25. manim/cli/default_group.py +157 -25
  26. manim/cli/init/commands.py +32 -24
  27. manim/cli/plugins/commands.py +16 -3
  28. manim/cli/render/commands.py +72 -60
  29. manim/cli/render/ease_of_access_options.py +4 -3
  30. manim/cli/render/global_options.py +51 -15
  31. manim/cli/render/output_options.py +6 -5
  32. manim/cli/render/render_options.py +97 -32
  33. manim/constants.py +65 -19
  34. manim/gui/gui.py +2 -0
  35. manim/mobject/frame.py +0 -1
  36. manim/mobject/geometry/arc.py +112 -78
  37. manim/mobject/geometry/boolean_ops.py +32 -25
  38. manim/mobject/geometry/labeled.py +300 -77
  39. manim/mobject/geometry/line.py +132 -64
  40. manim/mobject/geometry/polygram.py +126 -30
  41. manim/mobject/geometry/shape_matchers.py +35 -15
  42. manim/mobject/geometry/tips.py +38 -29
  43. manim/mobject/graph.py +414 -133
  44. manim/mobject/graphing/coordinate_systems.py +126 -64
  45. manim/mobject/graphing/functions.py +25 -15
  46. manim/mobject/graphing/number_line.py +24 -10
  47. manim/mobject/graphing/probability.py +2 -10
  48. manim/mobject/graphing/scale.py +6 -5
  49. manim/mobject/matrix.py +17 -19
  50. manim/mobject/mobject.py +314 -165
  51. manim/mobject/opengl/opengl_compatibility.py +2 -0
  52. manim/mobject/opengl/opengl_geometry.py +30 -9
  53. manim/mobject/opengl/opengl_image_mobject.py +2 -0
  54. manim/mobject/opengl/opengl_mobject.py +509 -343
  55. manim/mobject/opengl/opengl_point_cloud_mobject.py +5 -7
  56. manim/mobject/opengl/opengl_surface.py +3 -2
  57. manim/mobject/opengl/opengl_three_dimensions.py +2 -0
  58. manim/mobject/opengl/opengl_vectorized_mobject.py +46 -79
  59. manim/mobject/svg/brace.py +63 -13
  60. manim/mobject/svg/svg_mobject.py +4 -3
  61. manim/mobject/table.py +11 -13
  62. manim/mobject/text/code_mobject.py +186 -548
  63. manim/mobject/text/numbers.py +9 -7
  64. manim/mobject/text/tex_mobject.py +23 -14
  65. manim/mobject/text/text_mobject.py +70 -24
  66. manim/mobject/three_d/polyhedra.py +98 -1
  67. manim/mobject/three_d/three_d_utils.py +4 -4
  68. manim/mobject/three_d/three_dimensions.py +62 -34
  69. manim/mobject/types/image_mobject.py +42 -24
  70. manim/mobject/types/point_cloud_mobject.py +105 -67
  71. manim/mobject/types/vectorized_mobject.py +496 -228
  72. manim/mobject/value_tracker.py +5 -4
  73. manim/mobject/vector_field.py +5 -5
  74. manim/opengl/__init__.py +3 -3
  75. manim/plugins/__init__.py +14 -1
  76. manim/plugins/plugins_flags.py +14 -8
  77. manim/renderer/cairo_renderer.py +20 -10
  78. manim/renderer/opengl_renderer.py +21 -23
  79. manim/renderer/opengl_renderer_window.py +2 -0
  80. manim/renderer/shader.py +2 -3
  81. manim/renderer/shader_wrapper.py +5 -2
  82. manim/renderer/vectorized_mobject_rendering.py +5 -0
  83. manim/scene/moving_camera_scene.py +23 -0
  84. manim/scene/scene.py +90 -43
  85. manim/scene/scene_file_writer.py +316 -165
  86. manim/scene/section.py +17 -15
  87. manim/scene/three_d_scene.py +13 -21
  88. manim/scene/vector_space_scene.py +22 -9
  89. manim/typing.py +830 -70
  90. manim/utils/bezier.py +1667 -399
  91. manim/utils/caching.py +13 -5
  92. manim/utils/color/AS2700.py +2 -0
  93. manim/utils/color/BS381.py +3 -0
  94. manim/utils/color/DVIPSNAMES.py +96 -0
  95. manim/utils/color/SVGNAMES.py +179 -0
  96. manim/utils/color/X11.py +3 -0
  97. manim/utils/color/XKCD.py +3 -0
  98. manim/utils/color/__init__.py +8 -5
  99. manim/utils/color/core.py +844 -309
  100. manim/utils/color/manim_colors.py +7 -9
  101. manim/utils/commands.py +48 -20
  102. manim/utils/config_ops.py +18 -13
  103. manim/utils/debug.py +8 -7
  104. manim/utils/deprecation.py +90 -40
  105. manim/utils/docbuild/__init__.py +17 -0
  106. manim/utils/docbuild/autoaliasattr_directive.py +234 -0
  107. manim/utils/docbuild/autocolor_directive.py +21 -17
  108. manim/utils/docbuild/manim_directive.py +50 -35
  109. manim/utils/docbuild/module_parsing.py +245 -0
  110. manim/utils/exceptions.py +6 -0
  111. manim/utils/family.py +5 -3
  112. manim/utils/family_ops.py +17 -4
  113. manim/utils/file_ops.py +26 -16
  114. manim/utils/hashing.py +9 -7
  115. manim/utils/images.py +10 -4
  116. manim/utils/ipython_magic.py +14 -8
  117. manim/utils/iterables.py +161 -119
  118. manim/utils/module_ops.py +57 -19
  119. manim/utils/opengl.py +83 -24
  120. manim/utils/parameter_parsing.py +32 -0
  121. manim/utils/paths.py +21 -23
  122. manim/utils/polylabel.py +168 -0
  123. manim/utils/qhull.py +218 -0
  124. manim/utils/rate_functions.py +74 -39
  125. manim/utils/simple_functions.py +24 -15
  126. manim/utils/sounds.py +7 -1
  127. manim/utils/space_ops.py +125 -69
  128. manim/utils/testing/__init__.py +17 -0
  129. manim/utils/testing/_frames_testers.py +13 -8
  130. manim/utils/testing/_show_diff.py +5 -3
  131. manim/utils/testing/_test_class_makers.py +33 -18
  132. manim/utils/testing/frames_comparison.py +27 -19
  133. manim/utils/tex.py +127 -197
  134. manim/utils/tex_file_writing.py +47 -45
  135. manim/utils/tex_templates.py +2 -1
  136. manim/utils/unit.py +6 -5
  137. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/LICENSE.community +1 -1
  138. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/METADATA +40 -39
  139. manim-0.19.0.dist-info/RECORD +221 -0
  140. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/WHEEL +1 -1
  141. manim/cli/new/__init__.py +0 -0
  142. manim/cli/new/group.py +0 -189
  143. manim/plugins/import_plugins.py +0 -43
  144. manim-0.18.0.post0.dist-info/RECORD +0 -217
  145. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/LICENSE +0 -0
  146. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/entry_points.txt +0 -0
@@ -4,7 +4,8 @@ from __future__ import annotations
4
4
 
5
5
  __all__ = ["DecimalNumber", "Integer", "Variable"]
6
6
 
7
- from typing import Sequence
7
+ from collections.abc import Sequence
8
+ from typing import Any
8
9
 
9
10
  import numpy as np
10
11
 
@@ -18,9 +19,11 @@ from manim.mobject.value_tracker import ValueTracker
18
19
 
19
20
  string_to_mob_map = {}
20
21
 
22
+ __all__ = ["DecimalNumber", "Integer", "Variable"]
23
+
21
24
 
22
25
  class DecimalNumber(VMobject, metaclass=ConvertToOpenGL):
23
- """An mobject representing a decimal number.
26
+ r"""An mobject representing a decimal number.
24
27
 
25
28
  Parameters
26
29
  ----------
@@ -208,10 +211,7 @@ class DecimalNumber(VMobject, metaclass=ConvertToOpenGL):
208
211
 
209
212
  rounded_num = np.round(number, self.num_decimal_places)
210
213
  if num_string.startswith("-") and rounded_num == 0:
211
- if self.include_sign:
212
- num_string = "+" + num_string[1:]
213
- else:
214
- num_string = num_string[1:]
214
+ num_string = "+" + num_string[1:] if self.include_sign else num_string[1:]
215
215
 
216
216
  return num_string
217
217
 
@@ -328,7 +328,9 @@ class Integer(DecimalNumber):
328
328
  self.add(Integer(number=6.28).set_x(-1.5).set_y(-2).set_color(YELLOW).scale(1.4))
329
329
  """
330
330
 
331
- def __init__(self, number=0, num_decimal_places=0, **kwargs):
331
+ def __init__(
332
+ self, number: float = 0, num_decimal_places: int = 0, **kwargs: Any
333
+ ) -> None:
332
334
  super().__init__(number=number, num_decimal_places=num_decimal_places, **kwargs)
333
335
 
334
336
  def get_value(self):
@@ -12,7 +12,7 @@ r"""Mobjects representing text rendered using LaTeX.
12
12
 
13
13
  from __future__ import annotations
14
14
 
15
- from manim.utils.color import ManimColor
15
+ from manim.utils.color import BLACK, ManimColor, ParsableManimColor
16
16
 
17
17
  __all__ = [
18
18
  "SingleStringMathTex",
@@ -26,15 +26,15 @@ __all__ = [
26
26
  import itertools as it
27
27
  import operator as op
28
28
  import re
29
+ from collections.abc import Iterable
29
30
  from functools import reduce
30
31
  from textwrap import dedent
31
- from typing import Iterable
32
32
 
33
33
  from manim import config, logger
34
34
  from manim.constants import *
35
35
  from manim.mobject.geometry.line import Line
36
36
  from manim.mobject.svg.svg_mobject import SVGMobject
37
- from manim.mobject.types.vectorized_mobject import VectorizedPoint, VGroup, VMobject
37
+ from manim.mobject.types.vectorized_mobject import VGroup, VMobject
38
38
  from manim.utils.tex import TexTemplate
39
39
  from manim.utils.tex_file_writing import tex_to_svg_file
40
40
 
@@ -62,12 +62,11 @@ class SingleStringMathTex(SVGMobject):
62
62
  tex_environment: str = "align*",
63
63
  tex_template: TexTemplate | None = None,
64
64
  font_size: float = DEFAULT_FONT_SIZE,
65
+ color: ParsableManimColor | None = None,
65
66
  **kwargs,
66
67
  ):
67
- if kwargs.get("color") is None:
68
- # makes it so that color isn't explicitly passed for these mobs,
69
- # and can instead inherit from the parent
70
- kwargs["color"] = VMobject().color
68
+ if color is None:
69
+ color = VMobject().color
71
70
 
72
71
  self._font_size = font_size
73
72
  self.organize_left_to_right = organize_left_to_right
@@ -88,6 +87,7 @@ class SingleStringMathTex(SVGMobject):
88
87
  should_center=should_center,
89
88
  stroke_width=stroke_width,
90
89
  height=height,
90
+ color=color,
91
91
  path_string_config={
92
92
  "should_subdivide_sharp_curves": True,
93
93
  "should_remove_null_curves": True,
@@ -175,8 +175,8 @@ class SingleStringMathTex(SVGMobject):
175
175
  tex = self._remove_stray_braces(tex)
176
176
 
177
177
  for context in ["array"]:
178
- begin_in = ("\\begin{%s}" % context) in tex
179
- end_in = ("\\end{%s}" % context) in tex
178
+ begin_in = ("\\begin{%s}" % context) in tex # noqa: UP031
179
+ end_in = ("\\end{%s}" % context) in tex # noqa: UP031
180
180
  if begin_in ^ end_in:
181
181
  # Just turn this into a blank string,
182
182
  # which means caller should leave a
@@ -191,7 +191,6 @@ class SingleStringMathTex(SVGMobject):
191
191
  This is important when the braces in the TeX code are spread over
192
192
  multiple arguments as in, e.g., ``MathTex(r"e^{i", r"\tau} = 1")``.
193
193
  """
194
-
195
194
  # "\{" does not count (it's a brace literal), but "\\{" counts (it's a new line and then brace)
196
195
  num_lefts = tex.count("{") - tex.count("\\{") + tex.count("\\\\{")
197
196
  num_rights = tex.count("}") - tex.count("\\}") + tex.count("\\\\}")
@@ -211,10 +210,16 @@ class SingleStringMathTex(SVGMobject):
211
210
  return self.tex_string
212
211
 
213
212
  def init_colors(self, propagate_colors=True):
214
- if config.renderer == RendererType.OPENGL:
215
- super().init_colors()
216
- elif config.renderer == RendererType.CAIRO:
217
- super().init_colors(propagate_colors=propagate_colors)
213
+ for submobject in self.submobjects:
214
+ # needed to preserve original (non-black)
215
+ # TeX colors of individual submobjects
216
+ if submobject.color != BLACK:
217
+ continue
218
+ submobject.color = self.color
219
+ if config.renderer == RendererType.OPENGL:
220
+ submobject.init_colors()
221
+ elif config.renderer == RendererType.CAIRO:
222
+ submobject.init_colors(propagate_colors=propagate_colors)
218
223
 
219
224
 
220
225
  class MathTex(SingleStringMathTex):
@@ -427,6 +432,10 @@ class MathTex(SingleStringMathTex):
427
432
  class Tex(MathTex):
428
433
  r"""A string compiled with LaTeX in normal mode.
429
434
 
435
+ The color can be set using
436
+ the ``color`` argument. Any parts of the ``tex_string`` that are colored by the
437
+ TeX commands ``\color`` or ``\textcolor`` will retain their original color.
438
+
430
439
  Tests
431
440
  -----
432
441
 
@@ -49,17 +49,18 @@ Examples
49
49
 
50
50
  from __future__ import annotations
51
51
 
52
+ import functools
53
+
52
54
  __all__ = ["Text", "Paragraph", "MarkupText", "register_font"]
53
55
 
54
56
 
55
57
  import copy
56
58
  import hashlib
57
- import os
58
59
  import re
60
+ from collections.abc import Iterable, Sequence
59
61
  from contextlib import contextmanager
60
62
  from itertools import chain
61
63
  from pathlib import Path
62
- from typing import Iterable, Sequence
63
64
 
64
65
  import manimpango
65
66
  import numpy as np
@@ -77,6 +78,8 @@ TEXT_MOB_SCALE_FACTOR = 0.05
77
78
  DEFAULT_LINE_SPACING_SCALE = 0.3
78
79
  TEXT2SVG_ADJUSTMENT_FACTOR = 4.8
79
80
 
81
+ __all__ = ["Text", "Paragraph", "MarkupText", "register_font"]
82
+
80
83
 
81
84
  def remove_invisible_chars(mobject: SVGMobject) -> SVGMobject:
82
85
  """Function to remove unwanted invisible characters from some mobjects.
@@ -131,10 +134,17 @@ class Paragraph(VGroup):
131
134
  --------
132
135
  Normal usage::
133
136
 
134
- paragraph = Paragraph('this is a awesome', 'paragraph',
135
- 'With \nNewlines', '\tWith Tabs',
136
- ' With Spaces', 'With Alignments',
137
- 'center', 'left', 'right')
137
+ paragraph = Paragraph(
138
+ "this is a awesome",
139
+ "paragraph",
140
+ "With \nNewlines",
141
+ "\tWith Tabs",
142
+ " With Spaces",
143
+ "With Alignments",
144
+ "center",
145
+ "left",
146
+ "right",
147
+ )
138
148
 
139
149
  Remove unwanted invisible characters::
140
150
 
@@ -350,7 +360,7 @@ class Text(SVGMobject):
350
360
  )
351
361
  text6.scale(1.3).shift(DOWN)
352
362
  self.add(text1, text2, text3, text4, text5 , text6)
353
- Group(*self.mobjects).arrange(DOWN, buff=.8).set_height(config.frame_height-LARGE_BUFF)
363
+ Group(*self.mobjects).arrange(DOWN, buff=.8).set(height=config.frame_height-LARGE_BUFF)
354
364
 
355
365
  .. manim:: TextMoreCustomization
356
366
  :save_last_frame:
@@ -407,6 +417,11 @@ class Text(SVGMobject):
407
417
 
408
418
  """
409
419
 
420
+ @staticmethod
421
+ @functools.cache
422
+ def font_list() -> list[str]:
423
+ return manimpango.list_fonts()
424
+
410
425
  def __init__(
411
426
  self,
412
427
  text: str,
@@ -431,13 +446,25 @@ class Text(SVGMobject):
431
446
  width: float = None,
432
447
  should_center: bool = True,
433
448
  disable_ligatures: bool = False,
449
+ use_svg_cache: bool = False,
434
450
  **kwargs,
435
451
  ) -> None:
436
452
  self.line_spacing = line_spacing
437
453
  if font and warn_missing_font:
438
- fonts_list = manimpango.list_fonts()
454
+ fonts_list = Text.font_list()
455
+ # handle special case of sans/sans-serif
456
+ if font.lower() == "sans-serif":
457
+ font = "sans"
439
458
  if font not in fonts_list:
440
- logger.warning(f"Font {font} not in {fonts_list}.")
459
+ # check if the capitalized version is in the supported fonts
460
+ if font.capitalize() in fonts_list:
461
+ font = font.capitalize()
462
+ elif font.lower() in fonts_list:
463
+ font = font.lower()
464
+ elif font.title() in fonts_list:
465
+ font = font.title()
466
+ else:
467
+ logger.warning(f"Font {font} not in {fonts_list}.")
441
468
  self.font = font
442
469
  self._font_size = float(font_size)
443
470
  # needs to be a float or else size is inflated when font_size = 24
@@ -491,7 +518,7 @@ class Text(SVGMobject):
491
518
  height=height,
492
519
  width=width,
493
520
  should_center=should_center,
494
- use_svg_cache=False,
521
+ use_svg_cache=use_svg_cache,
495
522
  **kwargs,
496
523
  )
497
524
  self.text = text
@@ -636,7 +663,8 @@ class Text(SVGMobject):
636
663
  )
637
664
  def _set_color_by_t2g(self, t2g=None):
638
665
  """Sets gradient colors for specified
639
- strings. Behaves similarly to ``set_color_by_t2c``."""
666
+ strings. Behaves similarly to ``set_color_by_t2c``.
667
+ """
640
668
  t2g = t2g if t2g else self.t2g
641
669
  for word, gradient in list(t2g.items()):
642
670
  for start, end in self._find_indexes(word, self.text):
@@ -675,7 +703,7 @@ class Text(SVGMobject):
675
703
  default = default_args[arg]
676
704
  if left != default and getattr(right_setting, arg) != default:
677
705
  raise ValueError(
678
- f"Ambiguous style for text '{self.text[right_setting.start:right_setting.end]}':"
706
+ f"Ambiguous style for text '{self.text[right_setting.start : right_setting.end]}':"
679
707
  + f"'{arg}' cannot be both '{left}' and '{right}'."
680
708
  )
681
709
  setattr(right_setting, arg, left if left != default else right)
@@ -886,7 +914,7 @@ class MarkupText(SVGMobject):
886
914
  Here is a list of supported tags:
887
915
 
888
916
  - ``<b>bold</b>``, ``<i>italic</i>`` and ``<b><i>bold+italic</i></b>``
889
- - ``<ul>underline</ul>`` and ``<s>strike through</s>``
917
+ - ``<u>underline</u>`` and ``<s>strike through</s>``
890
918
  - ``<tt>typewriter font</tt>``
891
919
  - ``<big>bigger font</big>`` and ``<small>smaller font</small>``
892
920
  - ``<sup>superscript</sup>`` and ``<sub>subscript</sub>``
@@ -1133,6 +1161,11 @@ class MarkupText(SVGMobject):
1133
1161
 
1134
1162
  """
1135
1163
 
1164
+ @staticmethod
1165
+ @functools.cache
1166
+ def font_list() -> list[str]:
1167
+ return manimpango.list_fonts()
1168
+
1136
1169
  def __init__(
1137
1170
  self,
1138
1171
  text: str,
@@ -1157,9 +1190,20 @@ class MarkupText(SVGMobject):
1157
1190
  self.text = text
1158
1191
  self.line_spacing = line_spacing
1159
1192
  if font and warn_missing_font:
1160
- fonts_list = manimpango.list_fonts()
1193
+ fonts_list = Text.font_list()
1194
+ # handle special case of sans/sans-serif
1195
+ if font.lower() == "sans-serif":
1196
+ font = "sans"
1161
1197
  if font not in fonts_list:
1162
- logger.warning(f"Font {font} not in {fonts_list}.")
1198
+ # check if the capitalized version is in the supported fonts
1199
+ if font.capitalize() in fonts_list:
1200
+ font = font.capitalize()
1201
+ elif font.lower() in fonts_list:
1202
+ font = font.lower()
1203
+ elif font.title() in fonts_list:
1204
+ font = font.title()
1205
+ else:
1206
+ logger.warning(f"Font {font} not in {fonts_list}.")
1163
1207
  self.font = font
1164
1208
  self._font_size = float(font_size)
1165
1209
  self.slant = slant
@@ -1268,15 +1312,13 @@ class MarkupText(SVGMobject):
1268
1312
  self.set_color_by_gradient(*self.gradient)
1269
1313
  for col in colormap:
1270
1314
  self.chars[
1271
- col["start"]
1272
- - col["start_offset"] : col["end"]
1315
+ col["start"] - col["start_offset"] : col["end"]
1273
1316
  - col["start_offset"]
1274
1317
  - col["end_offset"]
1275
1318
  ].set_color(self._parse_color(col["color"]))
1276
1319
  for grad in gradientmap:
1277
1320
  self.chars[
1278
- grad["start"]
1279
- - grad["start_offset"] : grad["end"]
1321
+ grad["start"] - grad["start_offset"] : grad["end"]
1280
1322
  - grad["start_offset"]
1281
1323
  - grad["end_offset"]
1282
1324
  ].set_color_by_gradient(
@@ -1369,7 +1411,8 @@ class MarkupText(SVGMobject):
1369
1411
  """Counts characters that will be displayed.
1370
1412
 
1371
1413
  This is needed for partial coloring or gradients, because space
1372
- counts to the text's `len`, but has no corresponding character."""
1414
+ counts to the text's `len`, but has no corresponding character.
1415
+ """
1373
1416
  count = 0
1374
1417
  level = 0
1375
1418
  # temporarily replace HTML entities by single char
@@ -1412,7 +1455,9 @@ class MarkupText(SVGMobject):
1412
1455
  "end_offset": end_offset,
1413
1456
  },
1414
1457
  )
1415
- self.text = re.sub("<gradient[^>]+>(.+?)</gradient>", r"\1", self.text, 0, re.S)
1458
+ self.text = re.sub(
1459
+ "<gradient[^>]+>(.+?)</gradient>", r"\1", self.text, count=0, flags=re.S
1460
+ )
1416
1461
  return gradientmap
1417
1462
 
1418
1463
  def _parse_color(self, col):
@@ -1454,7 +1499,9 @@ class MarkupText(SVGMobject):
1454
1499
  "end_offset": end_offset,
1455
1500
  },
1456
1501
  )
1457
- self.text = re.sub("<color[^>]+>(.+?)</color>", r"\1", self.text, 0, re.S)
1502
+ self.text = re.sub(
1503
+ "<color[^>]+>(.+?)</color>", r"\1", self.text, count=0, flags=re.S
1504
+ )
1458
1505
  return colormap
1459
1506
 
1460
1507
  def __repr__(self):
@@ -1500,7 +1547,6 @@ def register_font(font_file: str | Path):
1500
1547
  This method is available for macOS for ``ManimPango>=v0.2.3``. Using this
1501
1548
  method with previous releases will raise an :class:`AttributeError` on macOS.
1502
1549
  """
1503
-
1504
1550
  input_folder = Path(config.input_file).parent.resolve()
1505
1551
  possible_paths = [
1506
1552
  Path(font_file),
@@ -1515,7 +1561,7 @@ def register_font(font_file: str | Path):
1515
1561
  logger.debug("Found file at %s", file_path.absolute())
1516
1562
  break
1517
1563
  else:
1518
- error = f"Can't find {font_file}." f"Tried these : {possible_paths}"
1564
+ error = f"Can't find {font_file}.Tried these : {possible_paths}"
1519
1565
  raise FileNotFoundError(error)
1520
1566
 
1521
1567
  try:
@@ -10,11 +10,20 @@ from manim.mobject.geometry.polygram import Polygon
10
10
  from manim.mobject.graph import Graph
11
11
  from manim.mobject.three_d.three_dimensions import Dot3D
12
12
  from manim.mobject.types.vectorized_mobject import VGroup
13
+ from manim.utils.qhull import QuickHull
13
14
 
14
15
  if TYPE_CHECKING:
15
16
  from manim.mobject.mobject import Mobject
17
+ from manim.typing import Point3D
16
18
 
17
- __all__ = ["Polyhedron", "Tetrahedron", "Octahedron", "Icosahedron", "Dodecahedron"]
19
+ __all__ = [
20
+ "Polyhedron",
21
+ "Tetrahedron",
22
+ "Octahedron",
23
+ "Icosahedron",
24
+ "Dodecahedron",
25
+ "ConvexHull3D",
26
+ ]
18
27
 
19
28
 
20
29
  class Polyhedron(VGroup):
@@ -361,3 +370,91 @@ class Dodecahedron(Polyhedron):
361
370
  ],
362
371
  **kwargs,
363
372
  )
373
+
374
+
375
+ class ConvexHull3D(Polyhedron):
376
+ """A convex hull for a set of points
377
+
378
+ Parameters
379
+ ----------
380
+ points
381
+ The points to consider.
382
+ tolerance
383
+ The tolerance used for quickhull.
384
+ kwargs
385
+ Forwarded to the parent constructor.
386
+
387
+ Examples
388
+ --------
389
+ .. manim:: ConvexHull3DExample
390
+ :save_last_frame:
391
+ :quality: high
392
+
393
+ class ConvexHull3DExample(ThreeDScene):
394
+ def construct(self):
395
+ self.set_camera_orientation(phi=75 * DEGREES, theta=30 * DEGREES)
396
+ points = [
397
+ [ 1.93192757, 0.44134585, -1.52407061],
398
+ [-0.93302521, 1.23206983, 0.64117067],
399
+ [-0.44350918, -0.61043677, 0.21723705],
400
+ [-0.42640268, -1.05260843, 1.61266094],
401
+ [-1.84449637, 0.91238739, -1.85172623],
402
+ [ 1.72068132, -0.11880457, 0.51881751],
403
+ [ 0.41904805, 0.44938012, -1.86440686],
404
+ [ 0.83864666, 1.66653337, 1.88960123],
405
+ [ 0.22240514, -0.80986286, 1.34249326],
406
+ [-1.29585759, 1.01516189, 0.46187522],
407
+ [ 1.7776499, -1.59550796, -1.70240747],
408
+ [ 0.80065226, -0.12530398, 1.70063977],
409
+ [ 1.28960948, -1.44158255, 1.39938582],
410
+ [-0.93538943, 1.33617705, -0.24852643],
411
+ [-1.54868271, 1.7444399, -0.46170734]
412
+ ]
413
+ hull = ConvexHull3D(
414
+ *points,
415
+ faces_config = {"stroke_opacity": 0},
416
+ graph_config = {
417
+ "vertex_type": Dot3D,
418
+ "edge_config": {
419
+ "stroke_color": BLUE,
420
+ "stroke_width": 2,
421
+ "stroke_opacity": 0.05,
422
+ }
423
+ }
424
+ )
425
+ dots = VGroup(*[Dot3D(point) for point in points])
426
+ self.add(hull)
427
+ self.add(dots)
428
+ """
429
+
430
+ def __init__(self, *points: Point3D, tolerance: float = 1e-5, **kwargs):
431
+ # Build Convex Hull
432
+ array = np.array(points)
433
+ hull = QuickHull(tolerance)
434
+ hull.build(array)
435
+
436
+ # Setup Lists
437
+ vertices = []
438
+ faces = []
439
+
440
+ # Extract Faces
441
+ c = 0
442
+ d = {}
443
+ facets = set(hull.facets) - hull.removed
444
+ for facet in facets:
445
+ tmp = set()
446
+ for subfacet in facet.subfacets:
447
+ for point in subfacet.points:
448
+ if point not in d:
449
+ vertices.append(point.coordinates)
450
+ d[point] = c
451
+ c += 1
452
+ tmp.add(point)
453
+ faces.append([d[point] for point in tmp])
454
+
455
+ # Call Polyhedron
456
+ super().__init__(
457
+ vertex_coords=vertices,
458
+ faces_list=faces,
459
+ **kwargs,
460
+ )
@@ -22,7 +22,7 @@ from manim.constants import ORIGIN, UP
22
22
  from manim.utils.space_ops import get_unit_normal
23
23
 
24
24
  if TYPE_CHECKING:
25
- from manim.typing import Point3D, Vector
25
+ from manim.typing import Point3D, Vector3D
26
26
 
27
27
 
28
28
  def get_3d_vmob_gradient_start_and_end_points(vmob) -> tuple[Point3D, Point3D]:
@@ -52,7 +52,7 @@ def get_3d_vmob_end_corner(vmob) -> Point3D:
52
52
  return vmob.points[get_3d_vmob_end_corner_index(vmob)]
53
53
 
54
54
 
55
- def get_3d_vmob_unit_normal(vmob, point_index: int) -> Vector:
55
+ def get_3d_vmob_unit_normal(vmob, point_index: int) -> Vector3D:
56
56
  n_points = vmob.get_num_points()
57
57
  if len(vmob.get_anchors()) <= 2:
58
58
  return np.array(UP)
@@ -68,9 +68,9 @@ def get_3d_vmob_unit_normal(vmob, point_index: int) -> Vector:
68
68
  return unit_normal
69
69
 
70
70
 
71
- def get_3d_vmob_start_corner_unit_normal(vmob) -> Vector:
71
+ def get_3d_vmob_start_corner_unit_normal(vmob) -> Vector3D:
72
72
  return get_3d_vmob_unit_normal(vmob, get_3d_vmob_start_corner_index(vmob))
73
73
 
74
74
 
75
- def get_3d_vmob_end_corner_unit_normal(vmob) -> Vector:
75
+ def get_3d_vmob_end_corner_unit_normal(vmob) -> Vector3D:
76
76
  return get_3d_vmob_unit_normal(vmob, get_3d_vmob_end_corner_index(vmob))