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
manim/_config/utils.py CHANGED
@@ -9,6 +9,7 @@ movie vs writing a single frame).
9
9
  See :doc:`/guides/configuration` for an introduction to Manim's configuration system.
10
10
 
11
11
  """
12
+
12
13
  from __future__ import annotations
13
14
 
14
15
  import argparse
@@ -19,17 +20,27 @@ import logging
19
20
  import os
20
21
  import re
21
22
  import sys
22
- from collections.abc import Mapping, MutableMapping
23
+ from collections.abc import Iterable, Iterator, Mapping, MutableMapping
23
24
  from pathlib import Path
24
- from typing import Any, Iterable, Iterator
25
+ from typing import TYPE_CHECKING, Any, ClassVar, NoReturn
25
26
 
26
27
  import numpy as np
27
28
 
28
- from .. import constants
29
- from ..constants import RendererType
30
- from ..typing import StrPath
31
- from ..utils.color import ManimColor
32
- from ..utils.tex import TexTemplate, TexTemplateFromFile
29
+ from manim import constants
30
+ from manim.constants import RendererType
31
+ from manim.utils.color import ManimColor
32
+ from manim.utils.tex import TexTemplate
33
+
34
+ if TYPE_CHECKING:
35
+ from enum import EnumMeta
36
+
37
+ from typing_extensions import Self
38
+
39
+ from manim.typing import StrPath, Vector3D
40
+
41
+ __all__ = ["config_file_paths", "make_config_parser", "ManimConfig", "ManimFrame"]
42
+
43
+ logger = logging.getLogger("manim")
33
44
 
34
45
 
35
46
  def config_file_paths() -> list[Path]:
@@ -237,8 +248,7 @@ class ManimConfig(MutableMapping):
237
248
  config.background_color = RED
238
249
 
239
250
 
240
- class MyScene(Scene):
241
- ...
251
+ class MyScene(Scene): ...
242
252
 
243
253
  the background color will be set to RED, regardless of the contents of
244
254
  ``manim.cfg`` or the CLI arguments used when invoking manim.
@@ -255,7 +265,6 @@ class ManimConfig(MutableMapping):
255
265
  "dry_run",
256
266
  "enable_wireframe",
257
267
  "ffmpeg_loglevel",
258
- "ffmpeg_executable",
259
268
  "format",
260
269
  "flush_cache",
261
270
  "frame_height",
@@ -310,10 +319,11 @@ class ManimConfig(MutableMapping):
310
319
  "zero_pad",
311
320
  "force_window",
312
321
  "no_latex_cleanup",
322
+ "preview_command",
313
323
  }
314
324
 
315
325
  def __init__(self) -> None:
316
- self._d = {k: None for k in self._OPTS}
326
+ self._d: dict[str, Any | None] = dict.fromkeys(self._OPTS)
317
327
 
318
328
  # behave like a dict
319
329
  def __iter__(self) -> Iterator[str]:
@@ -322,20 +332,20 @@ class ManimConfig(MutableMapping):
322
332
  def __len__(self) -> int:
323
333
  return len(self._d)
324
334
 
325
- def __contains__(self, key) -> bool:
335
+ def __contains__(self, key: object) -> bool:
326
336
  try:
327
337
  self.__getitem__(key)
328
338
  return True
329
339
  except AttributeError:
330
340
  return False
331
341
 
332
- def __getitem__(self, key) -> Any:
342
+ def __getitem__(self, key: str) -> Any:
333
343
  return getattr(self, key)
334
344
 
335
345
  def __setitem__(self, key: str, val: Any) -> None:
336
346
  getattr(ManimConfig, key).fset(self, val) # fset is the property's setter
337
347
 
338
- def update(self, obj: ManimConfig | dict) -> None:
348
+ def update(self, obj: ManimConfig | dict[str, Any]) -> None: # type: ignore[override]
339
349
  """Digest the options found in another :class:`ManimConfig` or in a dict.
340
350
 
341
351
  Similar to :meth:`dict.update`, replaces the values of this object with
@@ -362,7 +372,6 @@ class ManimConfig(MutableMapping):
362
372
  :meth:`~ManimConfig.digest_parser`
363
373
 
364
374
  """
365
-
366
375
  if isinstance(obj, ManimConfig):
367
376
  self._d.update(obj._d)
368
377
  if obj.tex_template:
@@ -379,14 +388,14 @@ class ManimConfig(MutableMapping):
379
388
  self[k] = v
380
389
 
381
390
  # don't allow to delete anything
382
- def __delitem__(self, key: str):
391
+ def __delitem__(self, key: str) -> NoReturn:
383
392
  raise AttributeError("'ManimConfig' object does not support item deletion")
384
393
 
385
- def __delattr__(self, key: str):
394
+ def __delattr__(self, key: str) -> NoReturn:
386
395
  raise AttributeError("'ManimConfig' object does not support item deletion")
387
396
 
388
397
  # copy functions
389
- def copy(self) -> ManimConfig:
398
+ def copy(self) -> Self:
390
399
  """Deepcopy the contents of this ManimConfig.
391
400
 
392
401
  Returns
@@ -405,13 +414,13 @@ class ManimConfig(MutableMapping):
405
414
  """
406
415
  return copy.deepcopy(self)
407
416
 
408
- def __copy__(self) -> ManimConfig:
417
+ def __copy__(self) -> Self:
409
418
  """See ManimConfig.copy()."""
410
419
  return copy.deepcopy(self)
411
420
 
412
- def __deepcopy__(self, memo: dict[str, Any]) -> ManimConfig:
421
+ def __deepcopy__(self, memo: dict[str, Any]) -> Self:
413
422
  """See ManimConfig.copy()."""
414
- c = ManimConfig()
423
+ c = type(self)()
415
424
  # Deepcopying the underlying dict is enough because all properties
416
425
  # either read directly from it or compute their value on the fly from
417
426
  # values read directly from it.
@@ -419,7 +428,7 @@ class ManimConfig(MutableMapping):
419
428
  return c
420
429
 
421
430
  # helper type-checking methods
422
- def _set_from_list(self, key: str, val: Any, values: list) -> None:
431
+ def _set_from_list(self, key: str, val: Any, values: list[Any]) -> None:
423
432
  """Set ``key`` to ``val`` if ``val`` is contained in ``values``."""
424
433
  if val in values:
425
434
  self._d[key] = val
@@ -451,14 +460,14 @@ class ManimConfig(MutableMapping):
451
460
  """
452
461
  self._d[key] = enum_class(enum_value)
453
462
 
454
- def _set_boolean(self, key: str | int, val: Any) -> None:
463
+ def _set_boolean(self, key: str, val: Any) -> None:
455
464
  """Set ``key`` to ``val`` if ``val`` is Boolean."""
456
465
  if val in [True, False]:
457
466
  self._d[key] = val
458
467
  else:
459
468
  raise ValueError(f"{key} must be boolean")
460
469
 
461
- def _set_tuple(self, key: str, val: tuple) -> None:
470
+ def _set_tuple(self, key: str, val: tuple[Any]) -> None:
462
471
  if isinstance(val, tuple):
463
472
  self._d[key] = val
464
473
  else:
@@ -507,7 +516,7 @@ class ManimConfig(MutableMapping):
507
516
  return rep
508
517
 
509
518
  # builders
510
- def digest_parser(self, parser: configparser.ConfigParser) -> ManimConfig:
519
+ def digest_parser(self, parser: configparser.ConfigParser) -> Self:
511
520
  """Process the config options present in a :class:`ConfigParser` object.
512
521
 
513
522
  This method processes arbitrary parsers, not only those read from a
@@ -633,17 +642,19 @@ class ManimConfig(MutableMapping):
633
642
  gui_location = tuple(
634
643
  map(int, re.split(r"[;,\-]", parser["CLI"]["gui_location"])),
635
644
  )
636
- setattr(self, "gui_location", gui_location)
645
+ self.gui_location = gui_location
637
646
 
638
647
  window_size = parser["CLI"][
639
648
  "window_size"
640
649
  ] # if not "default", get a tuple of the position
641
650
  if window_size != "default":
642
651
  window_size = tuple(map(int, re.split(r"[;,\-]", window_size)))
643
- setattr(self, "window_size", window_size)
652
+ self.window_size = window_size
644
653
 
645
654
  # plugins
646
- self.plugins = parser["CLI"].get("plugins", fallback="", raw=True).split(",")
655
+ plugins = parser["CLI"].get("plugins", fallback="", raw=True)
656
+ plugins = [] if plugins == "" else plugins.split(",")
657
+ self.plugins = plugins
647
658
  # the next two must be set AFTER digesting pixel_width and pixel_height
648
659
  self["frame_height"] = parser["CLI"].getfloat("frame_height", 8.0)
649
660
  width = parser["CLI"].getfloat("frame_width", None)
@@ -659,25 +670,21 @@ class ManimConfig(MutableMapping):
659
670
 
660
671
  val = parser["CLI"].get("progress_bar")
661
672
  if val:
662
- setattr(self, "progress_bar", val)
673
+ self.progress_bar = val
663
674
 
664
675
  val = parser["ffmpeg"].get("loglevel")
665
676
  if val:
666
677
  self.ffmpeg_loglevel = val
667
678
 
668
- # TODO: Fix the mess above and below
669
- val = parser["ffmpeg"].get("ffmpeg_executable")
670
- setattr(self, "ffmpeg_executable", val)
671
-
672
679
  try:
673
680
  val = parser["jupyter"].getboolean("media_embed")
674
681
  except ValueError:
675
682
  val = None
676
- setattr(self, "media_embed", val)
683
+ self.media_embed = val
677
684
 
678
685
  val = parser["jupyter"].get("media_width")
679
686
  if val:
680
- setattr(self, "media_width", val)
687
+ self.media_width = val
681
688
 
682
689
  val = parser["CLI"].get("quality", fallback="", raw=True)
683
690
  if val:
@@ -685,7 +692,7 @@ class ManimConfig(MutableMapping):
685
692
 
686
693
  return self
687
694
 
688
- def digest_args(self, args: argparse.Namespace) -> ManimConfig:
695
+ def digest_args(self, args: argparse.Namespace) -> Self:
689
696
  """Process the config options present in CLI arguments.
690
697
 
691
698
  Parameters
@@ -759,6 +766,7 @@ class ManimConfig(MutableMapping):
759
766
  "force_window",
760
767
  "dry_run",
761
768
  "no_latex_cleanup",
769
+ "preview_command",
762
770
  ]:
763
771
  if hasattr(args, key):
764
772
  attr = getattr(args, key)
@@ -789,7 +797,7 @@ class ManimConfig(MutableMapping):
789
797
  try:
790
798
  self.upto_animation_number = nflag[1]
791
799
  except Exception:
792
- logging.getLogger("manim").info(
800
+ logger.info(
793
801
  f"No end scene number specified in -n option. Rendering from {nflag[0]} onwards...",
794
802
  )
795
803
 
@@ -825,22 +833,19 @@ class ManimConfig(MutableMapping):
825
833
 
826
834
  # Handle --tex_template
827
835
  if args.tex_template:
828
- self.tex_template = TexTemplateFromFile(tex_filename=args.tex_template)
836
+ self.tex_template = TexTemplate.from_file(args.tex_template)
829
837
 
830
- if (
831
- self.renderer == RendererType.OPENGL
832
- and getattr(args, "write_to_movie") is None
833
- ):
838
+ if self.renderer == RendererType.OPENGL and args.write_to_movie is None:
834
839
  # --write_to_movie was not passed on the command line, so don't generate video.
835
840
  self["write_to_movie"] = False
836
841
 
837
842
  # Handle --gui_location flag.
838
- if getattr(args, "gui_location") is not None:
843
+ if args.gui_location is not None:
839
844
  self.gui_location = args.gui_location
840
845
 
841
846
  return self
842
847
 
843
- def digest_file(self, filename: str | os.PathLike) -> ManimConfig:
848
+ def digest_file(self, filename: StrPath) -> Self:
844
849
  """Process the config options present in a ``.cfg`` file.
845
850
 
846
851
  This method processes a single ``.cfg`` file, whereas
@@ -881,302 +886,403 @@ class ManimConfig(MutableMapping):
881
886
  return self.digest_parser(make_config_parser(filename))
882
887
 
883
888
  # config options are properties
884
- preview = property(
885
- lambda self: self._d["preview"] or self._d["enable_gui"],
886
- lambda self, val: self._set_boolean("preview", val),
887
- doc="Whether to play the rendered movie (-p).",
888
- )
889
-
890
- show_in_file_browser = property(
891
- lambda self: self._d["show_in_file_browser"],
892
- lambda self, val: self._set_boolean("show_in_file_browser", val),
893
- doc="Whether to show the output file in the file browser (-f).",
894
- )
895
-
896
- progress_bar = property(
897
- lambda self: self._d["progress_bar"],
898
- lambda self, val: self._set_from_list(
899
- "progress_bar",
900
- val,
901
- ["none", "display", "leave"],
902
- ),
903
- doc="Whether to show progress bars while rendering animations.",
904
- )
905
-
906
- log_to_file = property(
907
- lambda self: self._d["log_to_file"],
908
- lambda self, val: self._set_boolean("log_to_file", val),
909
- doc="Whether to save logs to a file.",
910
- )
911
-
912
- notify_outdated_version = property(
913
- lambda self: self._d["notify_outdated_version"],
914
- lambda self, val: self._set_boolean("notify_outdated_version", val),
915
- doc="Whether to notify if there is a version update available.",
916
- )
917
-
918
- write_to_movie = property(
919
- lambda self: self._d["write_to_movie"],
920
- lambda self, val: self._set_boolean("write_to_movie", val),
921
- doc="Whether to render the scene to a movie file (-w).",
922
- )
923
-
924
- save_last_frame = property(
925
- lambda self: self._d["save_last_frame"],
926
- lambda self, val: self._set_boolean("save_last_frame", val),
927
- doc="Whether to save the last frame of the scene as an image file (-s).",
928
- )
929
-
930
- write_all = property(
931
- lambda self: self._d["write_all"],
932
- lambda self, val: self._set_boolean("write_all", val),
933
- doc="Whether to render all scenes in the input file (-a).",
934
- )
935
-
936
- save_pngs = property(
937
- lambda self: self._d["save_pngs"],
938
- lambda self, val: self._set_boolean("save_pngs", val),
939
- doc="Whether to save all frames in the scene as images files (-g).",
940
- )
941
-
942
- save_as_gif = property(
943
- lambda self: self._d["save_as_gif"],
944
- lambda self, val: self._set_boolean("save_as_gif", val),
945
- doc="Whether to save the rendered scene in .gif format (-i).",
946
- )
947
-
948
- save_sections = property(
949
- lambda self: self._d["save_sections"],
950
- lambda self, val: self._set_boolean("save_sections", val),
951
- doc="Whether to save single videos for each section in addition to the movie file.",
952
- )
953
-
954
- enable_wireframe = property(
955
- lambda self: self._d["enable_wireframe"],
956
- lambda self, val: self._set_boolean("enable_wireframe", val),
957
- doc="Enable wireframe debugging mode in opengl.",
958
- )
959
-
960
- force_window = property(
961
- lambda self: self._d["force_window"],
962
- lambda self, val: self._set_boolean("force_window", val),
963
- doc="Set to force window when using the opengl renderer",
964
- )
965
-
966
- no_latex_cleanup = property(
967
- lambda self: self._d["no_latex_cleanup"],
968
- lambda self, val: self._set_boolean("no_latex_cleanup", val),
969
- doc="Prevents deletion of .aux, .dvi, and .log files produced by Tex and MathTex.",
970
- )
971
-
972
- @property
973
- def verbosity(self):
889
+
890
+ @property
891
+ def preview(self) -> bool:
892
+ """Whether to play the rendered movie (-p)."""
893
+ return self._d["preview"] or self._d["enable_gui"]
894
+
895
+ @preview.setter
896
+ def preview(self, value: bool) -> None:
897
+ self._set_boolean("preview", value)
898
+
899
+ @property
900
+ def show_in_file_browser(self) -> bool:
901
+ """Whether to show the output file in the file browser (-f)."""
902
+ return self._d["show_in_file_browser"]
903
+
904
+ @show_in_file_browser.setter
905
+ def show_in_file_browser(self, value: bool) -> None:
906
+ self._set_boolean("show_in_file_browser", value)
907
+
908
+ @property
909
+ def progress_bar(self) -> str:
910
+ """Whether to show progress bars while rendering animations."""
911
+ return self._d["progress_bar"]
912
+
913
+ @progress_bar.setter
914
+ def progress_bar(self, value: str) -> None:
915
+ self._set_from_list("progress_bar", value, ["none", "display", "leave"])
916
+
917
+ @property
918
+ def log_to_file(self) -> bool:
919
+ """Whether to save logs to a file."""
920
+ return self._d["log_to_file"]
921
+
922
+ @log_to_file.setter
923
+ def log_to_file(self, value: bool) -> None:
924
+ self._set_boolean("log_to_file", value)
925
+
926
+ @property
927
+ def notify_outdated_version(self) -> bool:
928
+ """Whether to notify if there is a version update available."""
929
+ return self._d["notify_outdated_version"]
930
+
931
+ @notify_outdated_version.setter
932
+ def notify_outdated_version(self, value: bool) -> None:
933
+ self._set_boolean("notify_outdated_version", value)
934
+
935
+ @property
936
+ def write_to_movie(self) -> bool:
937
+ """Whether to render the scene to a movie file (-w)."""
938
+ return self._d["write_to_movie"]
939
+
940
+ @write_to_movie.setter
941
+ def write_to_movie(self, value: bool) -> None:
942
+ self._set_boolean("write_to_movie", value)
943
+
944
+ @property
945
+ def save_last_frame(self) -> bool:
946
+ """Whether to save the last frame of the scene as an image file (-s)."""
947
+ return self._d["save_last_frame"]
948
+
949
+ @save_last_frame.setter
950
+ def save_last_frame(self, value: bool) -> None:
951
+ self._set_boolean("save_last_frame", value)
952
+
953
+ @property
954
+ def write_all(self) -> bool:
955
+ """Whether to render all scenes in the input file (-a)."""
956
+ return self._d["write_all"]
957
+
958
+ @write_all.setter
959
+ def write_all(self, value: bool) -> None:
960
+ self._set_boolean("write_all", value)
961
+
962
+ @property
963
+ def save_pngs(self) -> bool:
964
+ """Whether to save all frames in the scene as images files (-g)."""
965
+ return self._d["save_pngs"]
966
+
967
+ @save_pngs.setter
968
+ def save_pngs(self, value: bool) -> None:
969
+ self._set_boolean("save_pngs", value)
970
+
971
+ @property
972
+ def save_as_gif(self) -> bool:
973
+ """Whether to save the rendered scene in .gif format (-i)."""
974
+ return self._d["save_as_gif"]
975
+
976
+ @save_as_gif.setter
977
+ def save_as_gif(self, value: bool) -> None:
978
+ self._set_boolean("save_as_gif", value)
979
+
980
+ @property
981
+ def save_sections(self) -> bool:
982
+ """Whether to save single videos for each section in addition to the movie file."""
983
+ return self._d["save_sections"]
984
+
985
+ @save_sections.setter
986
+ def save_sections(self, value: bool) -> None:
987
+ self._set_boolean("save_sections", value)
988
+
989
+ @property
990
+ def enable_wireframe(self) -> bool:
991
+ """Whether to enable wireframe debugging mode in opengl."""
992
+ return self._d["enable_wireframe"]
993
+
994
+ @enable_wireframe.setter
995
+ def enable_wireframe(self, value: bool) -> None:
996
+ self._set_boolean("enable_wireframe", value)
997
+
998
+ @property
999
+ def force_window(self) -> bool:
1000
+ """Whether to force window when using the opengl renderer."""
1001
+ return self._d["force_window"]
1002
+
1003
+ @force_window.setter
1004
+ def force_window(self, value: bool) -> None:
1005
+ self._set_boolean("force_window", value)
1006
+
1007
+ @property
1008
+ def no_latex_cleanup(self) -> bool:
1009
+ """Prevents deletion of .aux, .dvi, and .log files produced by Tex and MathTex."""
1010
+ return self._d["no_latex_cleanup"]
1011
+
1012
+ @no_latex_cleanup.setter
1013
+ def no_latex_cleanup(self, value: bool) -> None:
1014
+ self._set_boolean("no_latex_cleanup", value)
1015
+
1016
+ @property
1017
+ def preview_command(self) -> str:
1018
+ return self._d["preview_command"]
1019
+
1020
+ @preview_command.setter
1021
+ def preview_command(self, value: str) -> None:
1022
+ self._set_str("preview_command", value)
1023
+
1024
+ @property
1025
+ def verbosity(self) -> str:
974
1026
  """Logger verbosity; "DEBUG", "INFO", "WARNING", "ERROR", or "CRITICAL" (-v)."""
975
1027
  return self._d["verbosity"]
976
1028
 
977
1029
  @verbosity.setter
978
1030
  def verbosity(self, val: str) -> None:
979
- """Verbosity level of the logger."""
980
1031
  self._set_from_list(
981
1032
  "verbosity",
982
1033
  val,
983
1034
  ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
984
1035
  )
985
- logging.getLogger("manim").setLevel(val)
1036
+ logger.setLevel(val)
986
1037
 
987
1038
  @property
988
- def format(self):
1039
+ def format(self) -> str:
989
1040
  """File format; "png", "gif", "mp4", "webm" or "mov"."""
990
1041
  return self._d["format"]
991
1042
 
992
1043
  @format.setter
993
1044
  def format(self, val: str) -> None:
994
- """File format the renderer will output."""
995
1045
  self._set_from_list(
996
1046
  "format",
997
1047
  val,
998
1048
  [None, "png", "gif", "mp4", "mov", "webm"],
999
1049
  )
1050
+ self.resolve_movie_file_extension(self.transparent)
1000
1051
  if self.format == "webm":
1001
- logging.getLogger("manim").warning(
1052
+ logger.warning(
1002
1053
  "Output format set as webm, this can be slower than other formats",
1003
1054
  )
1004
1055
 
1005
- ffmpeg_loglevel = property(
1006
- lambda self: self._d["ffmpeg_loglevel"],
1007
- lambda self, val: self._set_from_list(
1056
+ @property
1057
+ def ffmpeg_loglevel(self) -> str:
1058
+ """Verbosity level of ffmpeg (no flag)."""
1059
+ return self._d["ffmpeg_loglevel"]
1060
+
1061
+ @ffmpeg_loglevel.setter
1062
+ def ffmpeg_loglevel(self, val: str) -> None:
1063
+ self._set_from_list(
1008
1064
  "ffmpeg_loglevel",
1009
1065
  val,
1010
1066
  ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
1011
- ),
1012
- doc="Verbosity level of ffmpeg (no flag).",
1013
- )
1014
-
1015
- ffmpeg_executable = property(
1016
- lambda self: self._d["ffmpeg_executable"],
1017
- lambda self, val: self._set_str("ffmpeg_executable", val),
1018
- doc="Manually specify the path to the ffmpeg executable",
1019
- )
1020
-
1021
- media_embed = property(
1022
- lambda self: self._d["media_embed"],
1023
- lambda self, val: self._d.__setitem__("media_embed", val),
1024
- doc="Embed videos in Jupyter notebook",
1025
- )
1026
-
1027
- media_width = property(
1028
- lambda self: self._d["media_width"],
1029
- lambda self, val: self._d.__setitem__("media_width", val),
1030
- doc="Media width in Jupyter notebook",
1031
- )
1032
-
1033
- pixel_width = property(
1034
- lambda self: self._d["pixel_width"],
1035
- lambda self, val: self._set_pos_number("pixel_width", val, False),
1036
- doc="Frame width in pixels (--resolution, -r).",
1037
- )
1038
-
1039
- pixel_height = property(
1040
- lambda self: self._d["pixel_height"],
1041
- lambda self, val: self._set_pos_number("pixel_height", val, False),
1042
- doc="Frame height in pixels (--resolution, -r).",
1043
- )
1044
-
1045
- aspect_ratio = property(
1046
- lambda self: self._d["pixel_width"] / self._d["pixel_height"],
1047
- doc="Aspect ratio (width / height) in pixels (--resolution, -r).",
1048
- )
1049
-
1050
- frame_height = property(
1051
- lambda self: self._d["frame_height"],
1052
- lambda self, val: self._d.__setitem__("frame_height", val),
1053
- doc="Frame height in logical units (no flag).",
1054
- )
1055
-
1056
- frame_width = property(
1057
- lambda self: self._d["frame_width"],
1058
- lambda self, val: self._d.__setitem__("frame_width", val),
1059
- doc="Frame width in logical units (no flag).",
1060
- )
1061
-
1062
- frame_y_radius = property(
1063
- lambda self: self._d["frame_height"] / 2,
1064
- lambda self, val: (
1065
- self._d.__setitem__("frame_y_radius", val)
1066
- or self._d.__setitem__("frame_height", 2 * val)
1067
- ),
1068
- doc="Half the frame height (no flag).",
1069
- )
1070
-
1071
- frame_x_radius = property(
1072
- lambda self: self._d["frame_width"] / 2,
1073
- lambda self, val: (
1074
- self._d.__setitem__("frame_x_radius", val)
1075
- or self._d.__setitem__("frame_width", 2 * val)
1076
- ),
1077
- doc="Half the frame width (no flag).",
1078
- )
1079
-
1080
- top = property(
1081
- lambda self: self.frame_y_radius * constants.UP,
1082
- doc="Coordinate at the center top of the frame.",
1083
- )
1084
-
1085
- bottom = property(
1086
- lambda self: self.frame_y_radius * constants.DOWN,
1087
- doc="Coordinate at the center bottom of the frame.",
1088
- )
1089
-
1090
- left_side = property(
1091
- lambda self: self.frame_x_radius * constants.LEFT,
1092
- doc="Coordinate at the middle left of the frame.",
1093
- )
1094
-
1095
- right_side = property(
1096
- lambda self: self.frame_x_radius * constants.RIGHT,
1097
- doc="Coordinate at the middle right of the frame.",
1098
- )
1099
-
1100
- frame_rate = property(
1101
- lambda self: self._d["frame_rate"],
1102
- lambda self, val: self._d.__setitem__("frame_rate", val),
1103
- doc="Frame rate in frames per second.",
1104
- )
1105
-
1106
- background_color = property(
1107
- lambda self: self._d["background_color"],
1108
- lambda self, val: self._d.__setitem__("background_color", ManimColor(val)),
1109
- doc="Background color of the scene (-c).",
1110
- )
1111
-
1112
- from_animation_number = property(
1113
- lambda self: self._d["from_animation_number"],
1114
- lambda self, val: self._d.__setitem__("from_animation_number", val),
1115
- doc="Start rendering animations at this number (-n).",
1116
- )
1117
-
1118
- upto_animation_number = property(
1119
- lambda self: self._d["upto_animation_number"],
1120
- lambda self, val: self._set_pos_number("upto_animation_number", val, True),
1121
- doc="Stop rendering animations at this nmber. Use -1 to avoid skipping (-n).",
1122
- )
1123
-
1124
- max_files_cached = property(
1125
- lambda self: self._d["max_files_cached"],
1126
- lambda self, val: self._set_pos_number("max_files_cached", val, True),
1127
- doc="Maximum number of files cached. Use -1 for infinity (no flag).",
1128
- )
1129
-
1130
- window_monitor = property(
1131
- lambda self: self._d["window_monitor"],
1132
- lambda self, val: self._set_pos_number("window_monitor", val, True),
1133
- doc="The monitor on which the scene will be rendered",
1134
- )
1135
- flush_cache = property(
1136
- lambda self: self._d["flush_cache"],
1137
- lambda self, val: self._set_boolean("flush_cache", val),
1138
- doc="Whether to delete all the cached partial movie files.",
1139
- )
1140
-
1141
- disable_caching = property(
1142
- lambda self: self._d["disable_caching"],
1143
- lambda self, val: self._set_boolean("disable_caching", val),
1144
- doc="Whether to use scene caching.",
1145
- )
1146
-
1147
- disable_caching_warning = property(
1148
- lambda self: self._d["disable_caching_warning"],
1149
- lambda self, val: self._set_boolean("disable_caching_warning", val),
1150
- doc="Whether a warning is raised if there are too much submobjects to hash.",
1151
- )
1152
-
1153
- movie_file_extension = property(
1154
- lambda self: self._d["movie_file_extension"],
1155
- lambda self, val: self._set_from_list(
1156
- "movie_file_extension",
1157
- val,
1158
- [".mp4", ".mov", ".webm"],
1159
- ),
1160
- doc="Either .mp4, .webm or .mov.",
1161
- )
1162
-
1163
- background_opacity = property(
1164
- lambda self: self._d["background_opacity"],
1165
- lambda self, val: self._set_between("background_opacity", val, 0, 1),
1166
- doc="A number between 0.0 (fully transparent) and 1.0 (fully opaque).",
1167
- )
1168
-
1169
- frame_size = property(
1170
- lambda self: (self._d["pixel_width"], self._d["pixel_height"]),
1171
- lambda self, tup: (
1172
- self._d.__setitem__("pixel_width", tup[0])
1173
- or self._d.__setitem__("pixel_height", tup[1])
1174
- ),
1175
- doc="Tuple with (pixel width, pixel height) (no flag).",
1176
- )
1177
-
1178
- @property
1179
- def quality(self):
1067
+ )
1068
+ logging.getLogger("libav").setLevel(self.ffmpeg_loglevel)
1069
+
1070
+ @property
1071
+ def media_embed(self) -> bool:
1072
+ """Whether to embed videos in Jupyter notebook."""
1073
+ return self._d["media_embed"]
1074
+
1075
+ @media_embed.setter
1076
+ def media_embed(self, value: bool) -> None:
1077
+ self._set_boolean("media_embed", value)
1078
+
1079
+ @property
1080
+ def media_width(self) -> str:
1081
+ """Media width in Jupyter notebook."""
1082
+ return self._d["media_width"]
1083
+
1084
+ @media_width.setter
1085
+ def media_width(self, value: str) -> None:
1086
+ self._set_str("media_width", value)
1087
+
1088
+ @property
1089
+ def pixel_width(self) -> int:
1090
+ """Frame width in pixels (--resolution, -r)."""
1091
+ return self._d["pixel_width"]
1092
+
1093
+ @pixel_width.setter
1094
+ def pixel_width(self, value: int) -> None:
1095
+ self._set_pos_number("pixel_width", value, False)
1096
+
1097
+ @property
1098
+ def pixel_height(self) -> int:
1099
+ """Frame height in pixels (--resolution, -r)."""
1100
+ return self._d["pixel_height"]
1101
+
1102
+ @pixel_height.setter
1103
+ def pixel_height(self, value: int) -> None:
1104
+ self._set_pos_number("pixel_height", value, False)
1105
+
1106
+ @property
1107
+ def aspect_ratio(self) -> int:
1108
+ """Aspect ratio (width / height) in pixels (--resolution, -r)."""
1109
+ return self._d["pixel_width"] / self._d["pixel_height"]
1110
+
1111
+ @property
1112
+ def frame_height(self) -> float:
1113
+ """Frame height in logical units (no flag)."""
1114
+ return self._d["frame_height"]
1115
+
1116
+ @frame_height.setter
1117
+ def frame_height(self, value: float) -> None:
1118
+ self._d.__setitem__("frame_height", value)
1119
+
1120
+ @property
1121
+ def frame_width(self) -> float:
1122
+ """Frame width in logical units (no flag)."""
1123
+ return self._d["frame_width"]
1124
+
1125
+ @frame_width.setter
1126
+ def frame_width(self, value: float) -> None:
1127
+ self._d.__setitem__("frame_width", value)
1128
+
1129
+ @property
1130
+ def frame_y_radius(self) -> float:
1131
+ """Half the frame height (no flag)."""
1132
+ return self._d["frame_height"] / 2
1133
+
1134
+ @frame_y_radius.setter
1135
+ def frame_y_radius(self, value: float) -> None:
1136
+ self._d.__setitem__("frame_y_radius", value) or self._d.__setitem__(
1137
+ "frame_height", 2 * value
1138
+ )
1139
+
1140
+ @property
1141
+ def frame_x_radius(self) -> float:
1142
+ """Half the frame width (no flag)."""
1143
+ return self._d["frame_width"] / 2
1144
+
1145
+ @frame_x_radius.setter
1146
+ def frame_x_radius(self, value: float) -> None:
1147
+ self._d.__setitem__("frame_x_radius", value) or self._d.__setitem__(
1148
+ "frame_width", 2 * value
1149
+ )
1150
+
1151
+ @property
1152
+ def top(self) -> Vector3D:
1153
+ """Coordinate at the center top of the frame."""
1154
+ return self.frame_y_radius * constants.UP
1155
+
1156
+ @property
1157
+ def bottom(self) -> Vector3D:
1158
+ """Coordinate at the center bottom of the frame."""
1159
+ return self.frame_y_radius * constants.DOWN
1160
+
1161
+ @property
1162
+ def left_side(self) -> Vector3D:
1163
+ """Coordinate at the middle left of the frame."""
1164
+ return self.frame_x_radius * constants.LEFT
1165
+
1166
+ @property
1167
+ def right_side(self) -> Vector3D:
1168
+ """Coordinate at the middle right of the frame."""
1169
+ return self.frame_x_radius * constants.RIGHT
1170
+
1171
+ @property
1172
+ def frame_rate(self) -> float:
1173
+ """Frame rate in frames per second."""
1174
+ return self._d["frame_rate"]
1175
+
1176
+ @frame_rate.setter
1177
+ def frame_rate(self, value: float) -> None:
1178
+ self._d.__setitem__("frame_rate", value)
1179
+
1180
+ # TODO: This was parsed before maybe add ManimColor(val), but results in circular import
1181
+ @property
1182
+ def background_color(self) -> ManimColor:
1183
+ """Background color of the scene (-c)."""
1184
+ return self._d["background_color"]
1185
+
1186
+ @background_color.setter
1187
+ def background_color(self, value: Any) -> None:
1188
+ self._d.__setitem__("background_color", ManimColor(value))
1189
+
1190
+ @property
1191
+ def from_animation_number(self) -> int:
1192
+ """Start rendering animations at this number (-n)."""
1193
+ return self._d["from_animation_number"]
1194
+
1195
+ @from_animation_number.setter
1196
+ def from_animation_number(self, value: int) -> None:
1197
+ self._d.__setitem__("from_animation_number", value)
1198
+
1199
+ @property
1200
+ def upto_animation_number(self) -> int:
1201
+ """Stop rendering animations at this number. Use -1 to avoid skipping (-n)."""
1202
+ return self._d["upto_animation_number"]
1203
+
1204
+ @upto_animation_number.setter
1205
+ def upto_animation_number(self, value: int) -> None:
1206
+ self._set_pos_number("upto_animation_number", value, True)
1207
+
1208
+ @property
1209
+ def max_files_cached(self) -> int:
1210
+ """Maximum number of files cached. Use -1 for infinity (no flag)."""
1211
+ return self._d["max_files_cached"]
1212
+
1213
+ @max_files_cached.setter
1214
+ def max_files_cached(self, value: int) -> None:
1215
+ self._set_pos_number("max_files_cached", value, True)
1216
+
1217
+ @property
1218
+ def window_monitor(self) -> int:
1219
+ """The monitor on which the scene will be rendered."""
1220
+ return self._d["window_monitor"]
1221
+
1222
+ @window_monitor.setter
1223
+ def window_monitor(self, value: int) -> None:
1224
+ self._set_pos_number("window_monitor", value, True)
1225
+
1226
+ @property
1227
+ def flush_cache(self) -> bool:
1228
+ """Whether to delete all the cached partial movie files."""
1229
+ return self._d["flush_cache"]
1230
+
1231
+ @flush_cache.setter
1232
+ def flush_cache(self, value: bool) -> None:
1233
+ self._set_boolean("flush_cache", value)
1234
+
1235
+ @property
1236
+ def disable_caching(self) -> bool:
1237
+ """Whether to use scene caching."""
1238
+ return self._d["disable_caching"]
1239
+
1240
+ @disable_caching.setter
1241
+ def disable_caching(self, value: bool) -> None:
1242
+ self._set_boolean("disable_caching", value)
1243
+
1244
+ @property
1245
+ def disable_caching_warning(self) -> bool:
1246
+ """Whether a warning is raised if there are too much submobjects to hash."""
1247
+ return self._d["disable_caching_warning"]
1248
+
1249
+ @disable_caching_warning.setter
1250
+ def disable_caching_warning(self, value: bool) -> None:
1251
+ self._set_boolean("disable_caching_warning", value)
1252
+
1253
+ @property
1254
+ def movie_file_extension(self) -> str:
1255
+ """Either .mp4, .webm or .mov."""
1256
+ return self._d["movie_file_extension"]
1257
+
1258
+ @movie_file_extension.setter
1259
+ def movie_file_extension(self, value: str) -> None:
1260
+ self._set_from_list("movie_file_extension", value, [".mp4", ".mov", ".webm"])
1261
+
1262
+ @property
1263
+ def background_opacity(self) -> float:
1264
+ """A number between 0.0 (fully transparent) and 1.0 (fully opaque)."""
1265
+ return self._d["background_opacity"]
1266
+
1267
+ @background_opacity.setter
1268
+ def background_opacity(self, value: float) -> None:
1269
+ self._set_between("background_opacity", value, 0, 1)
1270
+ if self.background_opacity < 1:
1271
+ self.resolve_movie_file_extension(is_transparent=True)
1272
+
1273
+ @property
1274
+ def frame_size(self) -> tuple[int, int]:
1275
+ """Tuple with (pixel width, pixel height) (no flag)."""
1276
+ return (self._d["pixel_width"], self._d["pixel_height"])
1277
+
1278
+ @frame_size.setter
1279
+ def frame_size(self, value: tuple[int, int]) -> None:
1280
+ self._d.__setitem__("pixel_width", value[0]) or self._d.__setitem__(
1281
+ "pixel_height", value[1]
1282
+ )
1283
+
1284
+ @property
1285
+ def quality(self) -> str | None:
1180
1286
  """Video quality (-q)."""
1181
1287
  keys = ["pixel_width", "pixel_height", "frame_rate"]
1182
1288
  q = {k: self[k] for k in keys}
@@ -1186,27 +1292,27 @@ class ManimConfig(MutableMapping):
1186
1292
  return None
1187
1293
 
1188
1294
  @quality.setter
1189
- def quality(self, qual: str) -> None:
1190
- if qual is None:
1295
+ def quality(self, value: str | None) -> None:
1296
+ if value is None:
1191
1297
  return
1192
- if qual not in constants.QUALITIES:
1298
+ if value not in constants.QUALITIES:
1193
1299
  raise KeyError(f"quality must be one of {list(constants.QUALITIES.keys())}")
1194
- q = constants.QUALITIES[qual]
1300
+ q = constants.QUALITIES[value]
1195
1301
  self.frame_size = q["pixel_width"], q["pixel_height"]
1196
1302
  self.frame_rate = q["frame_rate"]
1197
1303
 
1198
1304
  @property
1199
- def transparent(self):
1200
- """Whether the background opacity is 0.0 (-t)."""
1201
- return self._d["background_opacity"] == 0.0
1305
+ def transparent(self) -> bool:
1306
+ """Whether the background opacity is less than 1.0 (-t)."""
1307
+ return self._d["background_opacity"] < 1.0
1202
1308
 
1203
1309
  @transparent.setter
1204
- def transparent(self, val: bool) -> None:
1205
- self._d["background_opacity"] = float(not val)
1206
- self.resolve_movie_file_extension(val)
1310
+ def transparent(self, value: bool) -> None:
1311
+ self._d["background_opacity"] = float(not value)
1312
+ self.resolve_movie_file_extension(value)
1207
1313
 
1208
1314
  @property
1209
- def dry_run(self):
1315
+ def dry_run(self) -> bool:
1210
1316
  """Whether dry run is enabled."""
1211
1317
  return self._d["dry_run"]
1212
1318
 
@@ -1220,7 +1326,7 @@ class ManimConfig(MutableMapping):
1220
1326
  self.format = None
1221
1327
 
1222
1328
  @property
1223
- def renderer(self):
1329
+ def renderer(self) -> RendererType:
1224
1330
  """The currently active renderer.
1225
1331
 
1226
1332
  Populated with one of the available renderers in :class:`.RendererType`.
@@ -1246,15 +1352,15 @@ class ManimConfig(MutableMapping):
1246
1352
  return self._d["renderer"]
1247
1353
 
1248
1354
  @renderer.setter
1249
- def renderer(self, val: str | RendererType) -> None:
1355
+ def renderer(self, value: str | RendererType) -> None:
1250
1356
  """The setter of the renderer property.
1251
1357
 
1252
1358
  Takes care of switching inheritance bases using the
1253
1359
  :class:`.ConvertToOpenGL` metaclass.
1254
1360
  """
1255
- if isinstance(val, str):
1256
- val = val.lower()
1257
- renderer = RendererType(val)
1361
+ if isinstance(value, str):
1362
+ value = value.lower()
1363
+ renderer = RendererType(value)
1258
1364
  try:
1259
1365
  from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL
1260
1366
  from manim.mobject.opengl.opengl_mobject import OpenGLMobject
@@ -1288,25 +1394,35 @@ class ManimConfig(MutableMapping):
1288
1394
 
1289
1395
  self._set_from_enum("renderer", renderer, RendererType)
1290
1396
 
1291
- media_dir = property(
1292
- lambda self: self._d["media_dir"],
1293
- lambda self, val: self._set_dir("media_dir", val),
1294
- doc="Main output directory. See :meth:`ManimConfig.get_dir`.",
1295
- )
1296
-
1297
- window_position = property(
1298
- lambda self: self._d["window_position"],
1299
- lambda self, val: self._d.__setitem__("window_position", val),
1300
- doc="Set the position of preview window. You can use directions, e.g. UL/DR/ORIGIN/LEFT...or the position(pixel) of the upper left corner of the window, e.g. '960,540'",
1301
- )
1302
-
1303
- window_size = property(
1304
- lambda self: self._d["window_size"],
1305
- lambda self, val: self._d.__setitem__("window_size", val),
1306
- doc="The size of the opengl window. 'default' to automatically scale the window based on the display monitor.",
1307
- )
1308
-
1309
- def resolve_movie_file_extension(self, is_transparent):
1397
+ @property
1398
+ def media_dir(self) -> str:
1399
+ """Main output directory. See :meth:`ManimConfig.get_dir`."""
1400
+ return self._d["media_dir"]
1401
+
1402
+ @media_dir.setter
1403
+ def media_dir(self, value: str | Path) -> None:
1404
+ self._set_dir("media_dir", value)
1405
+
1406
+ @property
1407
+ def window_position(self) -> str:
1408
+ """Set the position of preview window. You can use directions, e.g. UL/DR/ORIGIN/LEFT...or the position(pixel) of the upper left corner of the window, e.g. '960,540'."""
1409
+ return self._d["window_position"]
1410
+
1411
+ @window_position.setter
1412
+ def window_position(self, value: str) -> None:
1413
+ self._d.__setitem__("window_position", value)
1414
+
1415
+ @property
1416
+ def window_size(self) -> str:
1417
+ """The size of the opengl window. 'default' to automatically scale the window based on the display monitor."""
1418
+ return self._d["window_size"]
1419
+
1420
+ @window_size.setter
1421
+ def window_size(self, value: str) -> None:
1422
+ self._d.__setitem__("window_size", value)
1423
+
1424
+ def resolve_movie_file_extension(self, is_transparent: bool) -> None:
1425
+ prev_file_extension = self.movie_file_extension
1310
1426
  if is_transparent:
1311
1427
  self.movie_file_extension = ".webm" if self.format == "webm" else ".mov"
1312
1428
  elif self.format == "webm":
@@ -1315,44 +1431,67 @@ class ManimConfig(MutableMapping):
1315
1431
  self.movie_file_extension = ".mov"
1316
1432
  else:
1317
1433
  self.movie_file_extension = ".mp4"
1434
+ if self.movie_file_extension != prev_file_extension:
1435
+ logger.warning(
1436
+ f"Output format changed to '{self.movie_file_extension}' "
1437
+ "to support transparency",
1438
+ )
1439
+
1440
+ @property
1441
+ def enable_gui(self) -> bool:
1442
+ """Enable GUI interaction."""
1443
+ return self._d["enable_gui"]
1444
+
1445
+ @enable_gui.setter
1446
+ def enable_gui(self, value: bool) -> None:
1447
+ self._set_boolean("enable_gui", value)
1448
+
1449
+ @property
1450
+ def gui_location(self) -> tuple[Any]:
1451
+ """Enable GUI interaction."""
1452
+ return self._d["gui_location"]
1453
+
1454
+ @gui_location.setter
1455
+ def gui_location(self, value: tuple[Any]) -> None:
1456
+ self._set_tuple("gui_location", value)
1318
1457
 
1319
- enable_gui = property(
1320
- lambda self: self._d["enable_gui"],
1321
- lambda self, val: self._set_boolean("enable_gui", val),
1322
- doc="Enable GUI interaction.",
1323
- )
1324
-
1325
- gui_location = property(
1326
- lambda self: self._d["gui_location"],
1327
- lambda self, val: self._set_tuple("gui_location", val),
1328
- doc="Enable GUI interaction.",
1329
- )
1330
-
1331
- fullscreen = property(
1332
- lambda self: self._d["fullscreen"],
1333
- lambda self, val: self._set_boolean("fullscreen", val),
1334
- doc="Expand the window to its maximum possible size.",
1335
- )
1336
-
1337
- use_projection_fill_shaders = property(
1338
- lambda self: self._d["use_projection_fill_shaders"],
1339
- lambda self, val: self._set_boolean("use_projection_fill_shaders", val),
1340
- doc="Use shaders for OpenGLVMobject fill which are compatible with transformation matrices.",
1341
- )
1342
-
1343
- use_projection_stroke_shaders = property(
1344
- lambda self: self._d["use_projection_stroke_shaders"],
1345
- lambda self, val: self._set_boolean("use_projection_stroke_shaders", val),
1346
- doc="Use shaders for OpenGLVMobject stroke which are compatible with transformation matrices.",
1347
- )
1348
-
1349
- zero_pad = property(
1350
- lambda self: self._d["zero_pad"],
1351
- lambda self, val: self._set_int_between("zero_pad", val, 0, 9),
1352
- doc="PNG zero padding. A number between 0 (no zero padding) and 9 (9 columns minimum).",
1353
- )
1354
-
1355
- def get_dir(self, key: str, **kwargs: str) -> Path:
1458
+ @property
1459
+ def fullscreen(self) -> bool:
1460
+ """Expand the window to its maximum possible size."""
1461
+ return self._d["fullscreen"]
1462
+
1463
+ @fullscreen.setter
1464
+ def fullscreen(self, value: bool) -> None:
1465
+ self._set_boolean("fullscreen", value)
1466
+
1467
+ @property
1468
+ def use_projection_fill_shaders(self) -> bool:
1469
+ """Use shaders for OpenGLVMobject fill which are compatible with transformation matrices."""
1470
+ return self._d["use_projection_fill_shaders"]
1471
+
1472
+ @use_projection_fill_shaders.setter
1473
+ def use_projection_fill_shaders(self, value: bool) -> None:
1474
+ self._set_boolean("use_projection_fill_shaders", value)
1475
+
1476
+ @property
1477
+ def use_projection_stroke_shaders(self) -> bool:
1478
+ """Use shaders for OpenGLVMobject stroke which are compatible with transformation matrices."""
1479
+ return self._d["use_projection_stroke_shaders"]
1480
+
1481
+ @use_projection_stroke_shaders.setter
1482
+ def use_projection_stroke_shaders(self, value: bool) -> None:
1483
+ self._set_boolean("use_projection_stroke_shaders", value)
1484
+
1485
+ @property
1486
+ def zero_pad(self) -> int:
1487
+ """PNG zero padding. A number between 0 (no zero padding) and 9 (9 columns minimum)."""
1488
+ return self._d["zero_pad"]
1489
+
1490
+ @zero_pad.setter
1491
+ def zero_pad(self, value: int) -> None:
1492
+ self._set_int_between("zero_pad", value, 0, 9)
1493
+
1494
+ def get_dir(self, key: str, **kwargs: Any) -> Path:
1356
1495
  """Resolve a config option that stores a directory.
1357
1496
 
1358
1497
  Config options that store directories may depend on one another. This
@@ -1503,110 +1642,146 @@ class ManimConfig(MutableMapping):
1503
1642
  ) from exc
1504
1643
  return Path(path) if path else None
1505
1644
 
1506
- def _set_dir(self, key: str, val: str | Path):
1645
+ def _set_dir(self, key: str, val: str | Path) -> None:
1507
1646
  if isinstance(val, Path):
1508
1647
  self._d.__setitem__(key, str(val))
1509
1648
  else:
1510
1649
  self._d.__setitem__(key, val)
1511
1650
 
1512
- assets_dir = property(
1513
- lambda self: self._d["assets_dir"],
1514
- lambda self, val: self._set_dir("assets_dir", val),
1515
- doc="Directory to locate video assets (no flag).",
1516
- )
1517
-
1518
- log_dir = property(
1519
- lambda self: self._d["log_dir"],
1520
- lambda self, val: self._set_dir("log_dir", val),
1521
- doc="Directory to place logs. See :meth:`ManimConfig.get_dir`.",
1522
- )
1523
-
1524
- video_dir = property(
1525
- lambda self: self._d["video_dir"],
1526
- lambda self, val: self._set_dir("video_dir", val),
1527
- doc="Directory to place videos (no flag). See :meth:`ManimConfig.get_dir`.",
1528
- )
1529
-
1530
- sections_dir = property(
1531
- lambda self: self._d["sections_dir"],
1532
- lambda self, val: self._set_dir("sections_dir", val),
1533
- doc="Directory to place section videos (no flag). See :meth:`ManimConfig.get_dir`.",
1534
- )
1535
-
1536
- images_dir = property(
1537
- lambda self: self._d["images_dir"],
1538
- lambda self, val: self._set_dir("images_dir", val),
1539
- doc="Directory to place images (no flag). See :meth:`ManimConfig.get_dir`.",
1540
- )
1541
-
1542
- text_dir = property(
1543
- lambda self: self._d["text_dir"],
1544
- lambda self, val: self._set_dir("text_dir", val),
1545
- doc="Directory to place text (no flag). See :meth:`ManimConfig.get_dir`.",
1546
- )
1547
-
1548
- tex_dir = property(
1549
- lambda self: self._d["tex_dir"],
1550
- lambda self, val: self._set_dir("tex_dir", val),
1551
- doc="Directory to place tex (no flag). See :meth:`ManimConfig.get_dir`.",
1552
- )
1553
-
1554
- partial_movie_dir = property(
1555
- lambda self: self._d["partial_movie_dir"],
1556
- lambda self, val: self._set_dir("partial_movie_dir", val),
1557
- doc="Directory to place partial movie files (no flag). See :meth:`ManimConfig.get_dir`.",
1558
- )
1559
-
1560
- custom_folders = property(
1561
- lambda self: self._d["custom_folders"],
1562
- lambda self, val: self._set_boolean("custom_folders", val),
1563
- doc="Whether to use custom folder output.",
1564
- )
1565
-
1566
- input_file = property(
1567
- lambda self: self._d["input_file"],
1568
- lambda self, val: self._set_dir("input_file", val),
1569
- doc="Input file name.",
1570
- )
1571
-
1572
- output_file = property(
1573
- lambda self: self._d["output_file"],
1574
- lambda self, val: self._set_dir("output_file", val),
1575
- doc="Output file name (-o).",
1576
- )
1577
-
1578
- scene_names = property(
1579
- lambda self: self._d["scene_names"],
1580
- lambda self, val: self._d.__setitem__("scene_names", val),
1581
- doc="Scenes to play from file.",
1582
- )
1583
-
1584
- @property
1585
- def tex_template(self):
1651
+ @property
1652
+ def assets_dir(self) -> str:
1653
+ """Directory to locate video assets (no flag)."""
1654
+ return self._d["assets_dir"]
1655
+
1656
+ @assets_dir.setter
1657
+ def assets_dir(self, value: str | Path) -> None:
1658
+ self._set_dir("assets_dir", value)
1659
+
1660
+ @property
1661
+ def log_dir(self) -> str:
1662
+ """Directory to place logs. See :meth:`ManimConfig.get_dir`."""
1663
+ return self._d["log_dir"]
1664
+
1665
+ @log_dir.setter
1666
+ def log_dir(self, value: str | Path) -> None:
1667
+ self._set_dir("log_dir", value)
1668
+
1669
+ @property
1670
+ def video_dir(self) -> str:
1671
+ """Directory to place videos (no flag). See :meth:`ManimConfig.get_dir`."""
1672
+ return self._d["video_dir"]
1673
+
1674
+ @video_dir.setter
1675
+ def video_dir(self, value: str | Path) -> None:
1676
+ self._set_dir("video_dir", value)
1677
+
1678
+ @property
1679
+ def sections_dir(self) -> str:
1680
+ """Directory to place section videos (no flag). See :meth:`ManimConfig.get_dir`."""
1681
+ return self._d["sections_dir"]
1682
+
1683
+ @sections_dir.setter
1684
+ def sections_dir(self, value: str | Path) -> None:
1685
+ self._set_dir("sections_dir", value)
1686
+
1687
+ @property
1688
+ def images_dir(self) -> str:
1689
+ """Directory to place images (no flag). See :meth:`ManimConfig.get_dir`."""
1690
+ return self._d["images_dir"]
1691
+
1692
+ @images_dir.setter
1693
+ def images_dir(self, value: str | Path) -> None:
1694
+ self._set_dir("images_dir", value)
1695
+
1696
+ @property
1697
+ def text_dir(self) -> str:
1698
+ """Directory to place text (no flag). See :meth:`ManimConfig.get_dir`."""
1699
+ return self._d["text_dir"]
1700
+
1701
+ @text_dir.setter
1702
+ def text_dir(self, value: str | Path) -> None:
1703
+ self._set_dir("text_dir", value)
1704
+
1705
+ @property
1706
+ def tex_dir(self) -> str:
1707
+ """Directory to place tex (no flag). See :meth:`ManimConfig.get_dir`."""
1708
+ return self._d["tex_dir"]
1709
+
1710
+ @tex_dir.setter
1711
+ def tex_dir(self, value: str | Path) -> None:
1712
+ self._set_dir("tex_dir", value)
1713
+
1714
+ @property
1715
+ def partial_movie_dir(self) -> str:
1716
+ """Directory to place partial movie files (no flag). See :meth:`ManimConfig.get_dir`."""
1717
+ return self._d["partial_movie_dir"]
1718
+
1719
+ @partial_movie_dir.setter
1720
+ def partial_movie_dir(self, value: str | Path) -> None:
1721
+ self._set_dir("partial_movie_dir", value)
1722
+
1723
+ @property
1724
+ def custom_folders(self) -> str:
1725
+ """Whether to use custom folder output."""
1726
+ return self._d["custom_folders"]
1727
+
1728
+ @custom_folders.setter
1729
+ def custom_folders(self, value: str | Path) -> None:
1730
+ self._set_dir("custom_folders", value)
1731
+
1732
+ @property
1733
+ def input_file(self) -> str:
1734
+ """Input file name."""
1735
+ return self._d["input_file"]
1736
+
1737
+ @input_file.setter
1738
+ def input_file(self, value: str | Path) -> None:
1739
+ self._set_dir("input_file", value)
1740
+
1741
+ @property
1742
+ def output_file(self) -> str:
1743
+ """Output file name (-o)."""
1744
+ return self._d["output_file"]
1745
+
1746
+ @output_file.setter
1747
+ def output_file(self, value: str | Path) -> None:
1748
+ self._set_dir("output_file", value)
1749
+
1750
+ @property
1751
+ def scene_names(self) -> list[str]:
1752
+ """Scenes to play from file."""
1753
+ return self._d["scene_names"]
1754
+
1755
+ @scene_names.setter
1756
+ def scene_names(self, value: list[str]) -> None:
1757
+ self._d.__setitem__("scene_names", value)
1758
+
1759
+ @property
1760
+ def tex_template(self) -> TexTemplate:
1586
1761
  """Template used when rendering Tex. See :class:`.TexTemplate`."""
1587
1762
  if not hasattr(self, "_tex_template") or not self._tex_template:
1588
1763
  fn = self._d["tex_template_file"]
1589
1764
  if fn:
1590
- self._tex_template = TexTemplateFromFile(tex_filename=fn)
1765
+ self._tex_template = TexTemplate.from_file(fn)
1591
1766
  else:
1592
1767
  self._tex_template = TexTemplate()
1593
1768
  return self._tex_template
1594
1769
 
1595
1770
  @tex_template.setter
1596
- def tex_template(self, val: TexTemplateFromFile | TexTemplate) -> None:
1597
- if isinstance(val, (TexTemplateFromFile, TexTemplate)):
1771
+ def tex_template(self, val: TexTemplate) -> None:
1772
+ if isinstance(val, TexTemplate):
1598
1773
  self._tex_template = val
1599
1774
 
1600
1775
  @property
1601
- def tex_template_file(self):
1602
- """File to read Tex template from (no flag). See :class:`.TexTemplateFromFile`."""
1776
+ def tex_template_file(self) -> Path:
1777
+ """File to read Tex template from (no flag). See :class:`.TexTemplate`."""
1603
1778
  return self._d["tex_template_file"]
1604
1779
 
1605
1780
  @tex_template_file.setter
1606
1781
  def tex_template_file(self, val: str) -> None:
1607
1782
  if val:
1608
1783
  if not os.access(val, os.R_OK):
1609
- logging.getLogger("manim").warning(
1784
+ logger.warning(
1610
1785
  f"Custom TeX template {val} not found or not readable.",
1611
1786
  )
1612
1787
  else:
@@ -1615,17 +1790,19 @@ class ManimConfig(MutableMapping):
1615
1790
  self._d["tex_template_file"] = val # actually set the falsy value
1616
1791
 
1617
1792
  @property
1618
- def plugins(self):
1793
+ def plugins(self) -> list[str]:
1619
1794
  """List of plugins to enable."""
1620
1795
  return self._d["plugins"]
1621
1796
 
1622
1797
  @plugins.setter
1623
- def plugins(self, value):
1798
+ def plugins(self, value: list[str]):
1624
1799
  self._d["plugins"] = value
1625
1800
 
1626
1801
 
1802
+ # TODO: to be used in the future - see PR #620
1803
+ # https://github.com/ManimCommunity/manim/pull/620
1627
1804
  class ManimFrame(Mapping):
1628
- _OPTS: set[str] = {
1805
+ _OPTS: ClassVar[set[str]] = {
1629
1806
  "pixel_width",
1630
1807
  "pixel_height",
1631
1808
  "aspect_ratio",
@@ -1638,7 +1815,7 @@ class ManimFrame(Mapping):
1638
1815
  "left_side",
1639
1816
  "right_side",
1640
1817
  }
1641
- _CONSTANTS: dict[str, np.ndarray] = {
1818
+ _CONSTANTS: ClassVar[dict[str, Vector3D]] = {
1642
1819
  "UP": np.array((0.0, 1.0, 0.0)),
1643
1820
  "DOWN": np.array((0.0, -1.0, 0.0)),
1644
1821
  "RIGHT": np.array((1.0, 0.0, 0.0)),
@@ -1655,6 +1832,8 @@ class ManimFrame(Mapping):
1655
1832
  "DR": np.array((1.0, -1.0, 0.0)),
1656
1833
  }
1657
1834
 
1835
+ _c: ManimConfig
1836
+
1658
1837
  def __init__(self, c: ManimConfig) -> None:
1659
1838
  if not isinstance(c, ManimConfig):
1660
1839
  raise TypeError("argument must be instance of 'ManimConfig'")
@@ -1671,20 +1850,20 @@ class ManimFrame(Mapping):
1671
1850
  else:
1672
1851
  raise KeyError(key)
1673
1852
 
1674
- def __iter__(self) -> Iterable:
1853
+ def __iter__(self) -> Iterable[str]:
1675
1854
  return iter(list(self._OPTS) + list(self._CONSTANTS))
1676
1855
 
1677
1856
  def __len__(self) -> int:
1678
1857
  return len(self._OPTS)
1679
1858
 
1680
1859
  # make this truly immutable
1681
- def __setattr__(self, attr, val) -> None:
1860
+ def __setattr__(self, attr: Any, val: Any) -> NoReturn:
1682
1861
  raise TypeError("'ManimFrame' object does not support item assignment")
1683
1862
 
1684
- def __setitem__(self, key, val) -> None:
1863
+ def __setitem__(self, key: Any, val: Any) -> NoReturn:
1685
1864
  raise TypeError("'ManimFrame' object does not support item assignment")
1686
1865
 
1687
- def __delitem__(self, key) -> None:
1866
+ def __delitem__(self, key: Any) -> NoReturn:
1688
1867
  raise TypeError("'ManimFrame' object does not support item deletion")
1689
1868
 
1690
1869