manim 0.17.0__py3-none-any.whl → 0.19.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. manim/__init__.py +11 -6
  2. manim/__main__.py +62 -19
  3. manim/_config/__init__.py +10 -9
  4. manim/_config/cli_colors.py +26 -9
  5. manim/_config/default.cfg +1 -3
  6. manim/_config/logger_utils.py +23 -13
  7. manim/_config/utils.py +662 -468
  8. manim/animation/animation.py +164 -18
  9. manim/animation/changing.py +34 -23
  10. manim/animation/composition.py +265 -67
  11. manim/animation/creation.py +208 -26
  12. manim/animation/fading.py +16 -18
  13. manim/animation/growing.py +35 -15
  14. manim/animation/indication.py +150 -76
  15. manim/animation/movement.py +56 -22
  16. manim/animation/numbers.py +64 -6
  17. manim/animation/rotation.py +78 -7
  18. manim/animation/specialized.py +6 -7
  19. manim/animation/speedmodifier.py +13 -10
  20. manim/animation/transform.py +14 -11
  21. manim/animation/transform_matching_parts.py +3 -4
  22. manim/animation/updaters/mobject_update_utils.py +152 -30
  23. manim/animation/updaters/update.py +10 -7
  24. manim/camera/camera.py +182 -118
  25. manim/camera/mapping_camera.py +34 -3
  26. manim/camera/moving_camera.py +95 -74
  27. manim/camera/multi_camera.py +23 -15
  28. manim/camera/three_d_camera.py +70 -52
  29. manim/cli/__init__.py +17 -0
  30. manim/cli/cfg/group.py +76 -44
  31. manim/cli/checkhealth/checks.py +192 -0
  32. manim/cli/checkhealth/commands.py +90 -0
  33. manim/cli/default_group.py +158 -25
  34. manim/cli/init/commands.py +33 -25
  35. manim/cli/plugins/commands.py +16 -3
  36. manim/cli/render/commands.py +72 -60
  37. manim/cli/render/ease_of_access_options.py +4 -3
  38. manim/cli/render/global_options.py +59 -17
  39. manim/cli/render/output_options.py +6 -5
  40. manim/cli/render/render_options.py +98 -33
  41. manim/constants.py +109 -59
  42. manim/data_structures.py +31 -0
  43. manim/mobject/frame.py +8 -5
  44. manim/mobject/geometry/__init__.py +1 -0
  45. manim/mobject/geometry/arc.py +277 -135
  46. manim/mobject/geometry/boolean_ops.py +32 -31
  47. manim/mobject/geometry/labeled.py +376 -0
  48. manim/mobject/geometry/line.py +192 -87
  49. manim/mobject/geometry/polygram.py +224 -58
  50. manim/mobject/geometry/shape_matchers.py +61 -25
  51. manim/mobject/geometry/tips.py +122 -48
  52. manim/mobject/graph.py +1027 -419
  53. manim/mobject/graphing/coordinate_systems.py +533 -278
  54. manim/mobject/graphing/functions.py +53 -32
  55. manim/mobject/graphing/number_line.py +123 -65
  56. manim/mobject/graphing/probability.py +88 -62
  57. manim/mobject/graphing/scale.py +33 -19
  58. manim/mobject/logo.py +118 -28
  59. manim/mobject/matrix.py +87 -83
  60. manim/mobject/mobject.py +912 -442
  61. manim/mobject/opengl/dot_cloud.py +16 -5
  62. manim/mobject/opengl/opengl_compatibility.py +4 -2
  63. manim/mobject/opengl/opengl_geometry.py +254 -153
  64. manim/mobject/opengl/opengl_image_mobject.py +3 -1
  65. manim/mobject/opengl/opengl_mobject.py +779 -482
  66. manim/mobject/opengl/opengl_point_cloud_mobject.py +41 -14
  67. manim/mobject/opengl/opengl_surface.py +14 -92
  68. manim/mobject/opengl/opengl_three_dimensions.py +12 -8
  69. manim/mobject/opengl/opengl_vectorized_mobject.py +98 -100
  70. manim/mobject/svg/brace.py +173 -41
  71. manim/mobject/svg/svg_mobject.py +139 -53
  72. manim/mobject/table.py +61 -68
  73. manim/mobject/text/code_mobject.py +193 -539
  74. manim/mobject/text/numbers.py +81 -34
  75. manim/mobject/text/tex_mobject.py +130 -78
  76. manim/mobject/text/text_mobject.py +288 -164
  77. manim/mobject/three_d/polyhedra.py +111 -13
  78. manim/mobject/three_d/three_d_utils.py +17 -8
  79. manim/mobject/three_d/three_dimensions.py +239 -106
  80. manim/mobject/types/image_mobject.py +50 -30
  81. manim/mobject/types/point_cloud_mobject.py +120 -75
  82. manim/mobject/types/vectorized_mobject.py +841 -408
  83. manim/mobject/value_tracker.py +105 -38
  84. manim/mobject/vector_field.py +50 -31
  85. manim/opengl/__init__.py +3 -3
  86. manim/plugins/__init__.py +14 -1
  87. manim/plugins/plugins_flags.py +10 -14
  88. manim/renderer/cairo_renderer.py +65 -50
  89. manim/renderer/opengl_renderer.py +89 -69
  90. manim/renderer/opengl_renderer_window.py +39 -18
  91. manim/renderer/shader.py +123 -87
  92. manim/renderer/shader_wrapper.py +44 -28
  93. manim/renderer/vectorized_mobject_rendering.py +38 -10
  94. manim/scene/moving_camera_scene.py +32 -3
  95. manim/scene/scene.py +507 -242
  96. manim/scene/scene_file_writer.py +371 -220
  97. manim/scene/section.py +20 -16
  98. manim/scene/three_d_scene.py +14 -22
  99. manim/scene/vector_space_scene.py +223 -129
  100. manim/scene/zoomed_scene.py +46 -41
  101. manim/typing.py +990 -0
  102. manim/utils/bezier.py +1823 -371
  103. manim/utils/caching.py +12 -5
  104. manim/utils/color/AS2700.py +236 -0
  105. manim/utils/color/BS381.py +318 -0
  106. manim/utils/color/DVIPSNAMES.py +96 -0
  107. manim/utils/color/SVGNAMES.py +179 -0
  108. manim/utils/color/X11.py +533 -0
  109. manim/utils/color/XKCD.py +952 -0
  110. manim/utils/color/__init__.py +61 -0
  111. manim/utils/color/core.py +1667 -0
  112. manim/utils/color/manim_colors.py +218 -0
  113. manim/utils/commands.py +48 -20
  114. manim/utils/config_ops.py +39 -19
  115. manim/utils/debug.py +8 -7
  116. manim/utils/deprecation.py +86 -39
  117. manim/utils/docbuild/__init__.py +17 -0
  118. manim/utils/docbuild/autoaliasattr_directive.py +236 -0
  119. manim/utils/docbuild/autocolor_directive.py +99 -0
  120. manim/utils/docbuild/manim_directive.py +94 -41
  121. manim/utils/docbuild/module_parsing.py +245 -0
  122. manim/utils/exceptions.py +6 -0
  123. manim/utils/family.py +5 -3
  124. manim/utils/family_ops.py +17 -4
  125. manim/utils/file_ops.py +27 -17
  126. manim/utils/hashing.py +55 -45
  127. manim/utils/images.py +13 -7
  128. manim/utils/ipython_magic.py +13 -7
  129. manim/utils/iterables.py +163 -120
  130. manim/utils/module_ops.py +66 -24
  131. manim/utils/opengl.py +77 -24
  132. manim/utils/parameter_parsing.py +32 -0
  133. manim/utils/paths.py +30 -33
  134. manim/utils/polylabel.py +235 -0
  135. manim/utils/qhull.py +218 -0
  136. manim/utils/rate_functions.py +98 -32
  137. manim/utils/simple_functions.py +25 -33
  138. manim/utils/sounds.py +7 -1
  139. manim/utils/space_ops.py +188 -115
  140. manim/utils/testing/__init__.py +17 -0
  141. manim/utils/testing/_frames_testers.py +13 -8
  142. manim/utils/testing/_show_diff.py +5 -3
  143. manim/utils/testing/_test_class_makers.py +34 -18
  144. manim/utils/testing/frames_comparison.py +37 -19
  145. manim/utils/tex.py +130 -198
  146. manim/utils/tex_file_writing.py +77 -47
  147. manim/utils/tex_templates.py +2 -1
  148. manim/utils/unit.py +6 -5
  149. {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/METADATA +64 -65
  150. manim-0.19.1.dist-info/RECORD +220 -0
  151. {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/WHEEL +1 -1
  152. manim-0.19.1.dist-info/entry_points.txt +3 -0
  153. {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE.community +1 -1
  154. manim/cli/new/group.py +0 -189
  155. manim/communitycolors.py +0 -9
  156. manim/gui/__init__.py +0 -0
  157. manim/gui/gui.py +0 -82
  158. manim/plugins/import_plugins.py +0 -43
  159. manim/utils/color.py +0 -552
  160. manim-0.17.0.dist-info/RECORD +0 -206
  161. manim-0.17.0.dist-info/entry_points.txt +0 -4
  162. /manim/cli/{new → checkhealth}/__init__.py +0 -0
  163. {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE +0 -0
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
- import colour
27
27
  import numpy as np
28
28
 
29
- from .. import constants
30
- from ..constants import RendererType
31
- from ..utils.tex import TexTemplate, TexTemplateFromFile
32
- from ..utils.tex_templates import TexTemplateLibrary
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]:
@@ -76,7 +87,7 @@ def config_file_paths() -> list[Path]:
76
87
 
77
88
 
78
89
  def make_config_parser(
79
- custom_file: str | os.PathLike | None = None,
90
+ custom_file: StrPath | None = None,
80
91
  ) -> configparser.ConfigParser:
81
92
  """Make a :class:`ConfigParser` object and load any ``.cfg`` files.
82
93
 
@@ -111,10 +122,14 @@ def make_config_parser(
111
122
  # read_file() before calling read() for any optional files."
112
123
  # https://docs.python.org/3/library/configparser.html#configparser.ConfigParser.read
113
124
  parser = configparser.ConfigParser()
125
+ logger.info(f"Reading config file: {library_wide}")
114
126
  with library_wide.open() as file:
115
127
  parser.read_file(file) # necessary file
116
128
 
117
129
  other_files = [user_wide, Path(custom_file) if custom_file else folder_wide]
130
+ for path in other_files:
131
+ if path.exists():
132
+ logger.info(f"Reading config file: {path}")
118
133
  parser.read(other_files) # optional files
119
134
 
120
135
  return parser
@@ -237,8 +252,7 @@ class ManimConfig(MutableMapping):
237
252
  config.background_color = RED
238
253
 
239
254
 
240
- class MyScene(Scene):
241
- ...
255
+ class MyScene(Scene): ...
242
256
 
243
257
  the background color will be set to RED, regardless of the contents of
244
258
  ``manim.cfg`` or the CLI arguments used when invoking manim.
@@ -255,7 +269,6 @@ class ManimConfig(MutableMapping):
255
269
  "dry_run",
256
270
  "enable_wireframe",
257
271
  "ffmpeg_loglevel",
258
- "ffmpeg_executable",
259
272
  "format",
260
273
  "flush_cache",
261
274
  "frame_height",
@@ -309,10 +322,12 @@ class ManimConfig(MutableMapping):
309
322
  "write_to_movie",
310
323
  "zero_pad",
311
324
  "force_window",
325
+ "no_latex_cleanup",
326
+ "preview_command",
312
327
  }
313
328
 
314
329
  def __init__(self) -> None:
315
- self._d = {k: None for k in self._OPTS}
330
+ self._d: dict[str, Any | None] = dict.fromkeys(self._OPTS)
316
331
 
317
332
  # behave like a dict
318
333
  def __iter__(self) -> Iterator[str]:
@@ -321,20 +336,20 @@ class ManimConfig(MutableMapping):
321
336
  def __len__(self) -> int:
322
337
  return len(self._d)
323
338
 
324
- def __contains__(self, key) -> bool:
339
+ def __contains__(self, key: object) -> bool:
325
340
  try:
326
341
  self.__getitem__(key)
327
342
  return True
328
343
  except AttributeError:
329
344
  return False
330
345
 
331
- def __getitem__(self, key) -> Any:
346
+ def __getitem__(self, key: str) -> Any:
332
347
  return getattr(self, key)
333
348
 
334
349
  def __setitem__(self, key: str, val: Any) -> None:
335
350
  getattr(ManimConfig, key).fset(self, val) # fset is the property's setter
336
351
 
337
- def update(self, obj: ManimConfig | dict) -> None:
352
+ def update(self, obj: ManimConfig | dict[str, Any]) -> None: # type: ignore[override]
338
353
  """Digest the options found in another :class:`ManimConfig` or in a dict.
339
354
 
340
355
  Similar to :meth:`dict.update`, replaces the values of this object with
@@ -361,7 +376,6 @@ class ManimConfig(MutableMapping):
361
376
  :meth:`~ManimConfig.digest_parser`
362
377
 
363
378
  """
364
-
365
379
  if isinstance(obj, ManimConfig):
366
380
  self._d.update(obj._d)
367
381
  if obj.tex_template:
@@ -378,14 +392,14 @@ class ManimConfig(MutableMapping):
378
392
  self[k] = v
379
393
 
380
394
  # don't allow to delete anything
381
- def __delitem__(self, key: str):
395
+ def __delitem__(self, key: str) -> NoReturn:
382
396
  raise AttributeError("'ManimConfig' object does not support item deletion")
383
397
 
384
- def __delattr__(self, key: str):
398
+ def __delattr__(self, key: str) -> NoReturn:
385
399
  raise AttributeError("'ManimConfig' object does not support item deletion")
386
400
 
387
401
  # copy functions
388
- def copy(self) -> ManimConfig:
402
+ def copy(self) -> Self:
389
403
  """Deepcopy the contents of this ManimConfig.
390
404
 
391
405
  Returns
@@ -404,13 +418,13 @@ class ManimConfig(MutableMapping):
404
418
  """
405
419
  return copy.deepcopy(self)
406
420
 
407
- def __copy__(self) -> ManimConfig:
421
+ def __copy__(self) -> Self:
408
422
  """See ManimConfig.copy()."""
409
423
  return copy.deepcopy(self)
410
424
 
411
- def __deepcopy__(self, memo: dict[str, Any]) -> ManimConfig:
425
+ def __deepcopy__(self, memo: dict[str, Any]) -> Self:
412
426
  """See ManimConfig.copy()."""
413
- c = ManimConfig()
427
+ c = type(self)()
414
428
  # Deepcopying the underlying dict is enough because all properties
415
429
  # either read directly from it or compute their value on the fly from
416
430
  # values read directly from it.
@@ -418,7 +432,7 @@ class ManimConfig(MutableMapping):
418
432
  return c
419
433
 
420
434
  # helper type-checking methods
421
- def _set_from_list(self, key: str, val: Any, values: list) -> None:
435
+ def _set_from_list(self, key: str, val: Any, values: list[Any]) -> None:
422
436
  """Set ``key`` to ``val`` if ``val`` is contained in ``values``."""
423
437
  if val in values:
424
438
  self._d[key] = val
@@ -450,14 +464,14 @@ class ManimConfig(MutableMapping):
450
464
  """
451
465
  self._d[key] = enum_class(enum_value)
452
466
 
453
- def _set_boolean(self, key: str | int, val: Any) -> None:
467
+ def _set_boolean(self, key: str, val: Any) -> None:
454
468
  """Set ``key`` to ``val`` if ``val`` is Boolean."""
455
469
  if val in [True, False]:
456
470
  self._d[key] = val
457
471
  else:
458
472
  raise ValueError(f"{key} must be boolean")
459
473
 
460
- def _set_tuple(self, key: str, val: tuple) -> None:
474
+ def _set_tuple(self, key: str, val: tuple[Any]) -> None:
461
475
  if isinstance(val, tuple):
462
476
  self._d[key] = val
463
477
  else:
@@ -506,7 +520,7 @@ class ManimConfig(MutableMapping):
506
520
  return rep
507
521
 
508
522
  # builders
509
- def digest_parser(self, parser: configparser.ConfigParser) -> ManimConfig:
523
+ def digest_parser(self, parser: configparser.ConfigParser) -> Self:
510
524
  """Process the config options present in a :class:`ConfigParser` object.
511
525
 
512
526
  This method processes arbitrary parsers, not only those read from a
@@ -580,6 +594,8 @@ class ManimConfig(MutableMapping):
580
594
  "use_projection_stroke_shaders",
581
595
  "enable_wireframe",
582
596
  "force_window",
597
+ "no_latex_cleanup",
598
+ "dry_run",
583
599
  ]:
584
600
  setattr(self, key, parser["CLI"].getboolean(key, fallback=False))
585
601
 
@@ -614,6 +630,7 @@ class ManimConfig(MutableMapping):
614
630
  "background_color",
615
631
  "renderer",
616
632
  "window_position",
633
+ "preview_command",
617
634
  ]:
618
635
  setattr(self, key, parser["CLI"].get(key, fallback="", raw=True))
619
636
 
@@ -631,17 +648,19 @@ class ManimConfig(MutableMapping):
631
648
  gui_location = tuple(
632
649
  map(int, re.split(r"[;,\-]", parser["CLI"]["gui_location"])),
633
650
  )
634
- setattr(self, "gui_location", gui_location)
651
+ self.gui_location = gui_location
635
652
 
636
653
  window_size = parser["CLI"][
637
654
  "window_size"
638
655
  ] # if not "default", get a tuple of the position
639
656
  if window_size != "default":
640
657
  window_size = tuple(map(int, re.split(r"[;,\-]", window_size)))
641
- setattr(self, "window_size", window_size)
658
+ self.window_size = window_size
642
659
 
643
660
  # plugins
644
- self.plugins = parser["CLI"].get("plugins", fallback="", raw=True).split(",")
661
+ plugins = parser["CLI"].get("plugins", fallback="", raw=True)
662
+ plugins = [] if plugins == "" else plugins.split(",")
663
+ self.plugins = plugins
645
664
  # the next two must be set AFTER digesting pixel_width and pixel_height
646
665
  self["frame_height"] = parser["CLI"].getfloat("frame_height", 8.0)
647
666
  width = parser["CLI"].getfloat("frame_width", None)
@@ -657,25 +676,21 @@ class ManimConfig(MutableMapping):
657
676
 
658
677
  val = parser["CLI"].get("progress_bar")
659
678
  if val:
660
- setattr(self, "progress_bar", val)
679
+ self.progress_bar = val
661
680
 
662
681
  val = parser["ffmpeg"].get("loglevel")
663
682
  if val:
664
683
  self.ffmpeg_loglevel = val
665
684
 
666
- # TODO: Fix the mess above and below
667
- val = parser["ffmpeg"].get("ffmpeg_executable")
668
- setattr(self, "ffmpeg_executable", val)
669
-
670
685
  try:
671
686
  val = parser["jupyter"].getboolean("media_embed")
672
687
  except ValueError:
673
688
  val = None
674
- setattr(self, "media_embed", val)
689
+ self.media_embed = val
675
690
 
676
691
  val = parser["jupyter"].get("media_width")
677
692
  if val:
678
- setattr(self, "media_width", val)
693
+ self.media_width = val
679
694
 
680
695
  val = parser["CLI"].get("quality", fallback="", raw=True)
681
696
  if val:
@@ -683,7 +698,7 @@ class ManimConfig(MutableMapping):
683
698
 
684
699
  return self
685
700
 
686
- def digest_args(self, args: argparse.Namespace) -> ManimConfig:
701
+ def digest_args(self, args: argparse.Namespace) -> Self:
687
702
  """Process the config options present in CLI arguments.
688
703
 
689
704
  Parameters
@@ -756,6 +771,8 @@ class ManimConfig(MutableMapping):
756
771
  "enable_wireframe",
757
772
  "force_window",
758
773
  "dry_run",
774
+ "no_latex_cleanup",
775
+ "preview_command",
759
776
  ]:
760
777
  if hasattr(args, key):
761
778
  attr = getattr(args, key)
@@ -786,7 +803,7 @@ class ManimConfig(MutableMapping):
786
803
  try:
787
804
  self.upto_animation_number = nflag[1]
788
805
  except Exception:
789
- logging.getLogger("manim").info(
806
+ logger.info(
790
807
  f"No end scene number specified in -n option. Rendering from {nflag[0]} onwards...",
791
808
  )
792
809
 
@@ -822,22 +839,19 @@ class ManimConfig(MutableMapping):
822
839
 
823
840
  # Handle --tex_template
824
841
  if args.tex_template:
825
- self.tex_template = TexTemplateFromFile(tex_filename=args.tex_template)
842
+ self.tex_template = TexTemplate.from_file(args.tex_template)
826
843
 
827
- if (
828
- self.renderer == RendererType.OPENGL
829
- and getattr(args, "write_to_movie") is None
830
- ):
844
+ if self.renderer == RendererType.OPENGL and args.write_to_movie is None:
831
845
  # --write_to_movie was not passed on the command line, so don't generate video.
832
846
  self["write_to_movie"] = False
833
847
 
834
848
  # Handle --gui_location flag.
835
- if getattr(args, "gui_location") is not None:
849
+ if args.gui_location is not None:
836
850
  self.gui_location = args.gui_location
837
851
 
838
852
  return self
839
853
 
840
- def digest_file(self, filename: str | os.PathLike) -> ManimConfig:
854
+ def digest_file(self, filename: StrPath) -> Self:
841
855
  """Process the config options present in a ``.cfg`` file.
842
856
 
843
857
  This method processes a single ``.cfg`` file, whereas
@@ -878,326 +892,433 @@ class ManimConfig(MutableMapping):
878
892
  return self.digest_parser(make_config_parser(filename))
879
893
 
880
894
  # config options are properties
881
- preview = property(
882
- lambda self: self._d["preview"] or self._d["enable_gui"],
883
- lambda self, val: self._set_boolean("preview", val),
884
- doc="Whether to play the rendered movie (-p).",
885
- )
886
-
887
- show_in_file_browser = property(
888
- lambda self: self._d["show_in_file_browser"],
889
- lambda self, val: self._set_boolean("show_in_file_browser", val),
890
- doc="Whether to show the output file in the file browser (-f).",
891
- )
892
-
893
- progress_bar = property(
894
- lambda self: self._d["progress_bar"],
895
- lambda self, val: self._set_from_list(
896
- "progress_bar",
897
- val,
898
- ["none", "display", "leave"],
899
- ),
900
- doc="Whether to show progress bars while rendering animations.",
901
- )
902
-
903
- log_to_file = property(
904
- lambda self: self._d["log_to_file"],
905
- lambda self, val: self._set_boolean("log_to_file", val),
906
- doc="Whether to save logs to a file.",
907
- )
908
-
909
- notify_outdated_version = property(
910
- lambda self: self._d["notify_outdated_version"],
911
- lambda self, val: self._set_boolean("notify_outdated_version", val),
912
- doc="Whether to notify if there is a version update available.",
913
- )
914
-
915
- write_to_movie = property(
916
- lambda self: self._d["write_to_movie"],
917
- lambda self, val: self._set_boolean("write_to_movie", val),
918
- doc="Whether to render the scene to a movie file (-w).",
919
- )
920
-
921
- save_last_frame = property(
922
- lambda self: self._d["save_last_frame"],
923
- lambda self, val: self._set_boolean("save_last_frame", val),
924
- doc="Whether to save the last frame of the scene as an image file (-s).",
925
- )
926
-
927
- write_all = property(
928
- lambda self: self._d["write_all"],
929
- lambda self, val: self._set_boolean("write_all", val),
930
- doc="Whether to render all scenes in the input file (-a).",
931
- )
932
-
933
- save_pngs = property(
934
- lambda self: self._d["save_pngs"],
935
- lambda self, val: self._set_boolean("save_pngs", val),
936
- doc="Whether to save all frames in the scene as images files (-g).",
937
- )
938
-
939
- save_as_gif = property(
940
- lambda self: self._d["save_as_gif"],
941
- lambda self, val: self._set_boolean("save_as_gif", val),
942
- doc="Whether to save the rendered scene in .gif format (-i).",
943
- )
944
-
945
- save_sections = property(
946
- lambda self: self._d["save_sections"],
947
- lambda self, val: self._set_boolean("save_sections", val),
948
- doc="Whether to save single videos for each section in addition to the movie file.",
949
- )
950
-
951
- enable_wireframe = property(
952
- lambda self: self._d["enable_wireframe"],
953
- lambda self, val: self._set_boolean("enable_wireframe", val),
954
- doc="Enable wireframe debugging mode in opengl.",
955
- )
956
-
957
- force_window = property(
958
- lambda self: self._d["force_window"],
959
- lambda self, val: self._set_boolean("force_window", val),
960
- doc="Set to force window when using the opengl renderer",
961
- )
962
-
963
- @property
964
- def verbosity(self):
895
+
896
+ @property
897
+ def preview(self) -> bool:
898
+ """Whether to play the rendered movie (-p)."""
899
+ return self._d["preview"] or self._d["enable_gui"]
900
+
901
+ @preview.setter
902
+ def preview(self, value: bool) -> None:
903
+ self._set_boolean("preview", value)
904
+
905
+ @property
906
+ def show_in_file_browser(self) -> bool:
907
+ """Whether to show the output file in the file browser (-f)."""
908
+ return self._d["show_in_file_browser"]
909
+
910
+ @show_in_file_browser.setter
911
+ def show_in_file_browser(self, value: bool) -> None:
912
+ self._set_boolean("show_in_file_browser", value)
913
+
914
+ @property
915
+ def progress_bar(self) -> str:
916
+ """Whether to show progress bars while rendering animations."""
917
+ return self._d["progress_bar"]
918
+
919
+ @progress_bar.setter
920
+ def progress_bar(self, value: str) -> None:
921
+ self._set_from_list("progress_bar", value, ["none", "display", "leave"])
922
+
923
+ @property
924
+ def log_to_file(self) -> bool:
925
+ """Whether to save logs to a file."""
926
+ return self._d["log_to_file"]
927
+
928
+ @log_to_file.setter
929
+ def log_to_file(self, value: bool) -> None:
930
+ self._set_boolean("log_to_file", value)
931
+
932
+ @property
933
+ def notify_outdated_version(self) -> bool:
934
+ """Whether to notify if there is a version update available."""
935
+ return self._d["notify_outdated_version"]
936
+
937
+ @notify_outdated_version.setter
938
+ def notify_outdated_version(self, value: bool) -> None:
939
+ self._set_boolean("notify_outdated_version", value)
940
+
941
+ @property
942
+ def write_to_movie(self) -> bool:
943
+ """Whether to render the scene to a movie file (-w)."""
944
+ return self._d["write_to_movie"]
945
+
946
+ @write_to_movie.setter
947
+ def write_to_movie(self, value: bool) -> None:
948
+ self._set_boolean("write_to_movie", value)
949
+
950
+ @property
951
+ def save_last_frame(self) -> bool:
952
+ """Whether to save the last frame of the scene as an image file (-s)."""
953
+ return self._d["save_last_frame"]
954
+
955
+ @save_last_frame.setter
956
+ def save_last_frame(self, value: bool) -> None:
957
+ self._set_boolean("save_last_frame", value)
958
+
959
+ @property
960
+ def write_all(self) -> bool:
961
+ """Whether to render all scenes in the input file (-a)."""
962
+ return self._d["write_all"]
963
+
964
+ @write_all.setter
965
+ def write_all(self, value: bool) -> None:
966
+ self._set_boolean("write_all", value)
967
+
968
+ @property
969
+ def save_pngs(self) -> bool:
970
+ """Whether to save all frames in the scene as images files (-g)."""
971
+ return self._d["save_pngs"]
972
+
973
+ @save_pngs.setter
974
+ def save_pngs(self, value: bool) -> None:
975
+ self._set_boolean("save_pngs", value)
976
+
977
+ @property
978
+ def save_as_gif(self) -> bool:
979
+ """Whether to save the rendered scene in .gif format (-i)."""
980
+ return self._d["save_as_gif"]
981
+
982
+ @save_as_gif.setter
983
+ def save_as_gif(self, value: bool) -> None:
984
+ self._set_boolean("save_as_gif", value)
985
+
986
+ @property
987
+ def save_sections(self) -> bool:
988
+ """Whether to save single videos for each section in addition to the movie file."""
989
+ return self._d["save_sections"]
990
+
991
+ @save_sections.setter
992
+ def save_sections(self, value: bool) -> None:
993
+ self._set_boolean("save_sections", value)
994
+
995
+ @property
996
+ def enable_wireframe(self) -> bool:
997
+ """Whether to enable wireframe debugging mode in opengl."""
998
+ return self._d["enable_wireframe"]
999
+
1000
+ @enable_wireframe.setter
1001
+ def enable_wireframe(self, value: bool) -> None:
1002
+ self._set_boolean("enable_wireframe", value)
1003
+
1004
+ @property
1005
+ def force_window(self) -> bool:
1006
+ """Whether to force window when using the opengl renderer."""
1007
+ return self._d["force_window"]
1008
+
1009
+ @force_window.setter
1010
+ def force_window(self, value: bool) -> None:
1011
+ self._set_boolean("force_window", value)
1012
+
1013
+ @property
1014
+ def no_latex_cleanup(self) -> bool:
1015
+ """Prevents deletion of .aux, .dvi, and .log files produced by Tex and MathTex."""
1016
+ return self._d["no_latex_cleanup"]
1017
+
1018
+ @no_latex_cleanup.setter
1019
+ def no_latex_cleanup(self, value: bool) -> None:
1020
+ self._set_boolean("no_latex_cleanup", value)
1021
+
1022
+ @property
1023
+ def preview_command(self) -> str:
1024
+ return self._d["preview_command"]
1025
+
1026
+ @preview_command.setter
1027
+ def preview_command(self, value: str) -> None:
1028
+ self._set_str("preview_command", value)
1029
+
1030
+ @property
1031
+ def verbosity(self) -> str:
965
1032
  """Logger verbosity; "DEBUG", "INFO", "WARNING", "ERROR", or "CRITICAL" (-v)."""
966
1033
  return self._d["verbosity"]
967
1034
 
968
1035
  @verbosity.setter
969
1036
  def verbosity(self, val: str) -> None:
970
- """Verbosity level of the logger."""
971
1037
  self._set_from_list(
972
1038
  "verbosity",
973
1039
  val,
974
1040
  ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
975
1041
  )
976
- logging.getLogger("manim").setLevel(val)
1042
+ logger.setLevel(val)
977
1043
 
978
1044
  @property
979
- def format(self):
1045
+ def format(self) -> str:
980
1046
  """File format; "png", "gif", "mp4", "webm" or "mov"."""
981
1047
  return self._d["format"]
982
1048
 
983
1049
  @format.setter
984
1050
  def format(self, val: str) -> None:
985
- """File format the renderer will output."""
986
1051
  self._set_from_list(
987
1052
  "format",
988
1053
  val,
989
1054
  [None, "png", "gif", "mp4", "mov", "webm"],
990
1055
  )
1056
+ self.resolve_movie_file_extension(self.transparent)
991
1057
  if self.format == "webm":
992
- logging.getLogger("manim").warning(
1058
+ logger.warning(
993
1059
  "Output format set as webm, this can be slower than other formats",
994
1060
  )
995
1061
 
996
- ffmpeg_loglevel = property(
997
- lambda self: self._d["ffmpeg_loglevel"],
998
- lambda self, val: self._set_from_list(
1062
+ @property
1063
+ def ffmpeg_loglevel(self) -> str:
1064
+ """Verbosity level of ffmpeg (no flag)."""
1065
+ return self._d["ffmpeg_loglevel"]
1066
+
1067
+ @ffmpeg_loglevel.setter
1068
+ def ffmpeg_loglevel(self, val: str) -> None:
1069
+ self._set_from_list(
999
1070
  "ffmpeg_loglevel",
1000
1071
  val,
1001
1072
  ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
1002
- ),
1003
- doc="Verbosity level of ffmpeg (no flag).",
1004
- )
1005
-
1006
- ffmpeg_executable = property(
1007
- lambda self: self._d["ffmpeg_executable"],
1008
- lambda self, val: self._set_str("ffmpeg_executable", val),
1009
- doc="Manually specify the path to the ffmpeg executable",
1010
- )
1011
-
1012
- media_embed = property(
1013
- lambda self: self._d["media_embed"],
1014
- lambda self, val: self._d.__setitem__("media_embed", val),
1015
- doc="Embed videos in Jupyter notebook",
1016
- )
1017
-
1018
- media_width = property(
1019
- lambda self: self._d["media_width"],
1020
- lambda self, val: self._d.__setitem__("media_width", val),
1021
- doc="Media width in Jupyter notebook",
1022
- )
1023
-
1024
- pixel_width = property(
1025
- lambda self: self._d["pixel_width"],
1026
- lambda self, val: self._set_pos_number("pixel_width", val, False),
1027
- doc="Frame width in pixels (--resolution, -r).",
1028
- )
1029
-
1030
- pixel_height = property(
1031
- lambda self: self._d["pixel_height"],
1032
- lambda self, val: self._set_pos_number("pixel_height", val, False),
1033
- doc="Frame height in pixels (--resolution, -r).",
1034
- )
1035
-
1036
- aspect_ratio = property(
1037
- lambda self: self._d["pixel_width"] / self._d["pixel_height"],
1038
- doc="Aspect ratio (width / height) in pixels (--resolution, -r).",
1039
- )
1040
-
1041
- frame_height = property(
1042
- lambda self: self._d["frame_height"],
1043
- lambda self, val: self._d.__setitem__("frame_height", val),
1044
- doc="Frame height in logical units (no flag).",
1045
- )
1046
-
1047
- frame_width = property(
1048
- lambda self: self._d["frame_width"],
1049
- lambda self, val: self._d.__setitem__("frame_width", val),
1050
- doc="Frame width in logical units (no flag).",
1051
- )
1052
-
1053
- frame_y_radius = property(
1054
- lambda self: self._d["frame_height"] / 2,
1055
- lambda self, val: (
1056
- self._d.__setitem__("frame_y_radius", val)
1057
- or self._d.__setitem__("frame_height", 2 * val)
1058
- ),
1059
- doc="Half the frame height (no flag).",
1060
- )
1061
-
1062
- frame_x_radius = property(
1063
- lambda self: self._d["frame_width"] / 2,
1064
- lambda self, val: (
1065
- self._d.__setitem__("frame_x_radius", val)
1066
- or self._d.__setitem__("frame_width", 2 * val)
1067
- ),
1068
- doc="Half the frame width (no flag).",
1069
- )
1070
-
1071
- top = property(
1072
- lambda self: self.frame_y_radius * constants.UP,
1073
- doc="Coordinate at the center top of the frame.",
1074
- )
1075
-
1076
- bottom = property(
1077
- lambda self: self.frame_y_radius * constants.DOWN,
1078
- doc="Coordinate at the center bottom of the frame.",
1079
- )
1080
-
1081
- left_side = property(
1082
- lambda self: self.frame_x_radius * constants.LEFT,
1083
- doc="Coordinate at the middle left of the frame.",
1084
- )
1085
-
1086
- right_side = property(
1087
- lambda self: self.frame_x_radius * constants.RIGHT,
1088
- doc="Coordinate at the middle right of the frame.",
1089
- )
1090
-
1091
- frame_rate = property(
1092
- lambda self: self._d["frame_rate"],
1093
- lambda self, val: self._d.__setitem__("frame_rate", val),
1094
- doc="Frame rate in frames per second.",
1095
- )
1096
-
1097
- background_color = property(
1098
- lambda self: self._d["background_color"],
1099
- lambda self, val: self._d.__setitem__("background_color", colour.Color(val)),
1100
- doc="Background color of the scene (-c).",
1101
- )
1102
-
1103
- from_animation_number = property(
1104
- lambda self: self._d["from_animation_number"],
1105
- lambda self, val: self._d.__setitem__("from_animation_number", val),
1106
- doc="Start rendering animations at this number (-n).",
1107
- )
1108
-
1109
- upto_animation_number = property(
1110
- lambda self: self._d["upto_animation_number"],
1111
- lambda self, val: self._set_pos_number("upto_animation_number", val, True),
1112
- doc="Stop rendering animations at this nmber. Use -1 to avoid skipping (-n).",
1113
- )
1114
-
1115
- max_files_cached = property(
1116
- lambda self: self._d["max_files_cached"],
1117
- lambda self, val: self._set_pos_number("max_files_cached", val, True),
1118
- doc="Maximum number of files cached. Use -1 for infinity (no flag).",
1119
- )
1120
-
1121
- window_monitor = property(
1122
- lambda self: self._d["window_monitor"],
1123
- lambda self, val: self._set_pos_number("window_monitor", val, True),
1124
- doc="The monitor on which the scene will be rendered",
1125
- )
1126
- flush_cache = property(
1127
- lambda self: self._d["flush_cache"],
1128
- lambda self, val: self._set_boolean("flush_cache", val),
1129
- doc="Whether to delete all the cached partial movie files.",
1130
- )
1131
-
1132
- disable_caching = property(
1133
- lambda self: self._d["disable_caching"],
1134
- lambda self, val: self._set_boolean("disable_caching", val),
1135
- doc="Whether to use scene caching.",
1136
- )
1137
-
1138
- disable_caching_warning = property(
1139
- lambda self: self._d["disable_caching_warning"],
1140
- lambda self, val: self._set_boolean("disable_caching_warning", val),
1141
- doc="Whether a warning is raised if there are too much submobjects to hash.",
1142
- )
1143
-
1144
- movie_file_extension = property(
1145
- lambda self: self._d["movie_file_extension"],
1146
- lambda self, val: self._set_from_list(
1147
- "movie_file_extension",
1148
- val,
1149
- [".mp4", ".mov", ".webm"],
1150
- ),
1151
- doc="Either .mp4, .webm or .mov.",
1152
- )
1153
-
1154
- background_opacity = property(
1155
- lambda self: self._d["background_opacity"],
1156
- lambda self, val: self._set_between("background_opacity", val, 0, 1),
1157
- doc="A number between 0.0 (fully transparent) and 1.0 (fully opaque).",
1158
- )
1159
-
1160
- frame_size = property(
1161
- lambda self: (self._d["pixel_width"], self._d["pixel_height"]),
1162
- lambda self, tup: (
1163
- self._d.__setitem__("pixel_width", tup[0])
1164
- or self._d.__setitem__("pixel_height", tup[1])
1165
- ),
1166
- doc="Tuple with (pixel width, pixel height) (no flag).",
1167
- )
1168
-
1169
- @property
1170
- def quality(self):
1073
+ )
1074
+ logging.getLogger("libav").setLevel(self.ffmpeg_loglevel)
1075
+
1076
+ @property
1077
+ def media_embed(self) -> bool:
1078
+ """Whether to embed videos in Jupyter notebook."""
1079
+ return self._d["media_embed"]
1080
+
1081
+ @media_embed.setter
1082
+ def media_embed(self, value: bool) -> None:
1083
+ self._set_boolean("media_embed", value)
1084
+
1085
+ @property
1086
+ def media_width(self) -> str:
1087
+ """Media width in Jupyter notebook."""
1088
+ return self._d["media_width"]
1089
+
1090
+ @media_width.setter
1091
+ def media_width(self, value: str) -> None:
1092
+ self._set_str("media_width", value)
1093
+
1094
+ @property
1095
+ def pixel_width(self) -> int:
1096
+ """Frame width in pixels (--resolution, -r)."""
1097
+ return self._d["pixel_width"]
1098
+
1099
+ @pixel_width.setter
1100
+ def pixel_width(self, value: int) -> None:
1101
+ self._set_pos_number("pixel_width", value, False)
1102
+
1103
+ @property
1104
+ def pixel_height(self) -> int:
1105
+ """Frame height in pixels (--resolution, -r)."""
1106
+ return self._d["pixel_height"]
1107
+
1108
+ @pixel_height.setter
1109
+ def pixel_height(self, value: int) -> None:
1110
+ self._set_pos_number("pixel_height", value, False)
1111
+
1112
+ @property
1113
+ def aspect_ratio(self) -> int:
1114
+ """Aspect ratio (width / height) in pixels (--resolution, -r)."""
1115
+ return self._d["pixel_width"] / self._d["pixel_height"]
1116
+
1117
+ @property
1118
+ def frame_height(self) -> float:
1119
+ """Frame height in logical units (no flag)."""
1120
+ return self._d["frame_height"]
1121
+
1122
+ @frame_height.setter
1123
+ def frame_height(self, value: float) -> None:
1124
+ self._d.__setitem__("frame_height", value)
1125
+
1126
+ @property
1127
+ def frame_width(self) -> float:
1128
+ """Frame width in logical units (no flag)."""
1129
+ return self._d["frame_width"]
1130
+
1131
+ @frame_width.setter
1132
+ def frame_width(self, value: float) -> None:
1133
+ self._d.__setitem__("frame_width", value)
1134
+
1135
+ @property
1136
+ def frame_y_radius(self) -> float:
1137
+ """Half the frame height (no flag)."""
1138
+ return self._d["frame_height"] / 2
1139
+
1140
+ @frame_y_radius.setter
1141
+ def frame_y_radius(self, value: float) -> None:
1142
+ self._d.__setitem__("frame_y_radius", value) or self._d.__setitem__(
1143
+ "frame_height", 2 * value
1144
+ )
1145
+
1146
+ @property
1147
+ def frame_x_radius(self) -> float:
1148
+ """Half the frame width (no flag)."""
1149
+ return self._d["frame_width"] / 2
1150
+
1151
+ @frame_x_radius.setter
1152
+ def frame_x_radius(self, value: float) -> None:
1153
+ self._d.__setitem__("frame_x_radius", value) or self._d.__setitem__(
1154
+ "frame_width", 2 * value
1155
+ )
1156
+
1157
+ @property
1158
+ def top(self) -> Vector3D:
1159
+ """Coordinate at the center top of the frame."""
1160
+ return self.frame_y_radius * constants.UP
1161
+
1162
+ @property
1163
+ def bottom(self) -> Vector3D:
1164
+ """Coordinate at the center bottom of the frame."""
1165
+ return self.frame_y_radius * constants.DOWN
1166
+
1167
+ @property
1168
+ def left_side(self) -> Vector3D:
1169
+ """Coordinate at the middle left of the frame."""
1170
+ return self.frame_x_radius * constants.LEFT
1171
+
1172
+ @property
1173
+ def right_side(self) -> Vector3D:
1174
+ """Coordinate at the middle right of the frame."""
1175
+ return self.frame_x_radius * constants.RIGHT
1176
+
1177
+ @property
1178
+ def frame_rate(self) -> float:
1179
+ """Frame rate in frames per second."""
1180
+ return self._d["frame_rate"]
1181
+
1182
+ @frame_rate.setter
1183
+ def frame_rate(self, value: float) -> None:
1184
+ self._d.__setitem__("frame_rate", value)
1185
+
1186
+ # TODO: This was parsed before maybe add ManimColor(val), but results in circular import
1187
+ @property
1188
+ def background_color(self) -> ManimColor:
1189
+ """Background color of the scene (-c)."""
1190
+ return self._d["background_color"]
1191
+
1192
+ @background_color.setter
1193
+ def background_color(self, value: Any) -> None:
1194
+ self._d.__setitem__("background_color", ManimColor(value))
1195
+
1196
+ @property
1197
+ def from_animation_number(self) -> int:
1198
+ """Start rendering animations at this number (-n)."""
1199
+ return self._d["from_animation_number"]
1200
+
1201
+ @from_animation_number.setter
1202
+ def from_animation_number(self, value: int) -> None:
1203
+ self._d.__setitem__("from_animation_number", value)
1204
+
1205
+ @property
1206
+ def upto_animation_number(self) -> int:
1207
+ """Stop rendering animations at this number. Use -1 to avoid skipping (-n)."""
1208
+ return self._d["upto_animation_number"]
1209
+
1210
+ @upto_animation_number.setter
1211
+ def upto_animation_number(self, value: int) -> None:
1212
+ self._set_pos_number("upto_animation_number", value, True)
1213
+
1214
+ @property
1215
+ def max_files_cached(self) -> int:
1216
+ """Maximum number of files cached. Use -1 for infinity (no flag)."""
1217
+ return self._d["max_files_cached"]
1218
+
1219
+ @max_files_cached.setter
1220
+ def max_files_cached(self, value: int) -> None:
1221
+ self._set_pos_number("max_files_cached", value, True)
1222
+
1223
+ @property
1224
+ def window_monitor(self) -> int:
1225
+ """The monitor on which the scene will be rendered."""
1226
+ return self._d["window_monitor"]
1227
+
1228
+ @window_monitor.setter
1229
+ def window_monitor(self, value: int) -> None:
1230
+ self._set_pos_number("window_monitor", value, True)
1231
+
1232
+ @property
1233
+ def flush_cache(self) -> bool:
1234
+ """Whether to delete all the cached partial movie files."""
1235
+ return self._d["flush_cache"]
1236
+
1237
+ @flush_cache.setter
1238
+ def flush_cache(self, value: bool) -> None:
1239
+ self._set_boolean("flush_cache", value)
1240
+
1241
+ @property
1242
+ def disable_caching(self) -> bool:
1243
+ """Whether to use scene caching."""
1244
+ return self._d["disable_caching"]
1245
+
1246
+ @disable_caching.setter
1247
+ def disable_caching(self, value: bool) -> None:
1248
+ self._set_boolean("disable_caching", value)
1249
+
1250
+ @property
1251
+ def disable_caching_warning(self) -> bool:
1252
+ """Whether a warning is raised if there are too much submobjects to hash."""
1253
+ return self._d["disable_caching_warning"]
1254
+
1255
+ @disable_caching_warning.setter
1256
+ def disable_caching_warning(self, value: bool) -> None:
1257
+ self._set_boolean("disable_caching_warning", value)
1258
+
1259
+ @property
1260
+ def movie_file_extension(self) -> str:
1261
+ """Either .mp4, .webm or .mov."""
1262
+ return self._d["movie_file_extension"]
1263
+
1264
+ @movie_file_extension.setter
1265
+ def movie_file_extension(self, value: str) -> None:
1266
+ self._set_from_list("movie_file_extension", value, [".mp4", ".mov", ".webm"])
1267
+
1268
+ @property
1269
+ def background_opacity(self) -> float:
1270
+ """A number between 0.0 (fully transparent) and 1.0 (fully opaque)."""
1271
+ return self._d["background_opacity"]
1272
+
1273
+ @background_opacity.setter
1274
+ def background_opacity(self, value: float) -> None:
1275
+ self._set_between("background_opacity", value, 0, 1)
1276
+ if self.background_opacity < 1:
1277
+ self.resolve_movie_file_extension(is_transparent=True)
1278
+
1279
+ @property
1280
+ def frame_size(self) -> tuple[int, int]:
1281
+ """Tuple with (pixel width, pixel height) (no flag)."""
1282
+ return (self._d["pixel_width"], self._d["pixel_height"])
1283
+
1284
+ @frame_size.setter
1285
+ def frame_size(self, value: tuple[int, int]) -> None:
1286
+ self._d.__setitem__("pixel_width", value[0]) or self._d.__setitem__(
1287
+ "pixel_height", value[1]
1288
+ )
1289
+
1290
+ @property
1291
+ def quality(self) -> str | None:
1171
1292
  """Video quality (-q)."""
1172
1293
  keys = ["pixel_width", "pixel_height", "frame_rate"]
1173
1294
  q = {k: self[k] for k in keys}
1174
1295
  for qual in constants.QUALITIES:
1175
- if all([q[k] == constants.QUALITIES[qual][k] for k in keys]):
1296
+ if all(q[k] == constants.QUALITIES[qual][k] for k in keys):
1176
1297
  return qual
1177
1298
  return None
1178
1299
 
1179
1300
  @quality.setter
1180
- def quality(self, qual: str) -> None:
1181
- if qual is None:
1301
+ def quality(self, value: str | None) -> None:
1302
+ if value is None:
1182
1303
  return
1183
- if qual not in constants.QUALITIES:
1304
+ if value not in constants.QUALITIES:
1184
1305
  raise KeyError(f"quality must be one of {list(constants.QUALITIES.keys())}")
1185
- q = constants.QUALITIES[qual]
1306
+ q = constants.QUALITIES[value]
1186
1307
  self.frame_size = q["pixel_width"], q["pixel_height"]
1187
1308
  self.frame_rate = q["frame_rate"]
1188
1309
 
1189
1310
  @property
1190
- def transparent(self):
1191
- """Whether the background opacity is 0.0 (-t)."""
1192
- return self._d["background_opacity"] == 0.0
1311
+ def transparent(self) -> bool:
1312
+ """Whether the background opacity is less than 1.0 (-t)."""
1313
+ return self._d["background_opacity"] < 1.0
1193
1314
 
1194
1315
  @transparent.setter
1195
- def transparent(self, val: bool) -> None:
1196
- self._d["background_opacity"] = float(not val)
1197
- self.resolve_movie_file_extension(val)
1316
+ def transparent(self, value: bool) -> None:
1317
+ self._d["background_opacity"] = float(not value)
1318
+ self.resolve_movie_file_extension(value)
1198
1319
 
1199
1320
  @property
1200
- def dry_run(self):
1321
+ def dry_run(self) -> bool:
1201
1322
  """Whether dry run is enabled."""
1202
1323
  return self._d["dry_run"]
1203
1324
 
@@ -1211,7 +1332,7 @@ class ManimConfig(MutableMapping):
1211
1332
  self.format = None
1212
1333
 
1213
1334
  @property
1214
- def renderer(self):
1335
+ def renderer(self) -> RendererType:
1215
1336
  """The currently active renderer.
1216
1337
 
1217
1338
  Populated with one of the available renderers in :class:`.RendererType`.
@@ -1237,15 +1358,15 @@ class ManimConfig(MutableMapping):
1237
1358
  return self._d["renderer"]
1238
1359
 
1239
1360
  @renderer.setter
1240
- def renderer(self, val: str | RendererType) -> None:
1361
+ def renderer(self, value: str | RendererType) -> None:
1241
1362
  """The setter of the renderer property.
1242
1363
 
1243
1364
  Takes care of switching inheritance bases using the
1244
1365
  :class:`.ConvertToOpenGL` metaclass.
1245
1366
  """
1246
- if isinstance(val, str):
1247
- val = val.lower()
1248
- renderer = RendererType(val)
1367
+ if isinstance(value, str):
1368
+ value = value.lower()
1369
+ renderer = RendererType(value)
1249
1370
  try:
1250
1371
  from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL
1251
1372
  from manim.mobject.opengl.opengl_mobject import OpenGLMobject
@@ -1279,25 +1400,35 @@ class ManimConfig(MutableMapping):
1279
1400
 
1280
1401
  self._set_from_enum("renderer", renderer, RendererType)
1281
1402
 
1282
- media_dir = property(
1283
- lambda self: self._d["media_dir"],
1284
- lambda self, val: self._set_dir("media_dir", val),
1285
- doc="Main output directory. See :meth:`ManimConfig.get_dir`.",
1286
- )
1287
-
1288
- window_position = property(
1289
- lambda self: self._d["window_position"],
1290
- lambda self, val: self._d.__setitem__("window_position", val),
1291
- 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'",
1292
- )
1293
-
1294
- window_size = property(
1295
- lambda self: self._d["window_size"],
1296
- lambda self, val: self._d.__setitem__("window_size", val),
1297
- doc="The size of the opengl window. 'default' to automatically scale the window based on the display monitor.",
1298
- )
1299
-
1300
- def resolve_movie_file_extension(self, is_transparent):
1403
+ @property
1404
+ def media_dir(self) -> str:
1405
+ """Main output directory. See :meth:`ManimConfig.get_dir`."""
1406
+ return self._d["media_dir"]
1407
+
1408
+ @media_dir.setter
1409
+ def media_dir(self, value: str | Path) -> None:
1410
+ self._set_dir("media_dir", value)
1411
+
1412
+ @property
1413
+ def window_position(self) -> str:
1414
+ """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'."""
1415
+ return self._d["window_position"]
1416
+
1417
+ @window_position.setter
1418
+ def window_position(self, value: str) -> None:
1419
+ self._d.__setitem__("window_position", value)
1420
+
1421
+ @property
1422
+ def window_size(self) -> str:
1423
+ """The size of the opengl window as 'width,height' or 'default' to automatically scale the window based on the display monitor."""
1424
+ return self._d["window_size"]
1425
+
1426
+ @window_size.setter
1427
+ def window_size(self, value: str) -> None:
1428
+ self._d.__setitem__("window_size", value)
1429
+
1430
+ def resolve_movie_file_extension(self, is_transparent: bool) -> None:
1431
+ prev_file_extension = self.movie_file_extension
1301
1432
  if is_transparent:
1302
1433
  self.movie_file_extension = ".webm" if self.format == "webm" else ".mov"
1303
1434
  elif self.format == "webm":
@@ -1306,44 +1437,67 @@ class ManimConfig(MutableMapping):
1306
1437
  self.movie_file_extension = ".mov"
1307
1438
  else:
1308
1439
  self.movie_file_extension = ".mp4"
1440
+ if self.movie_file_extension != prev_file_extension:
1441
+ logger.warning(
1442
+ f"Output format changed to '{self.movie_file_extension}' "
1443
+ "to support transparency",
1444
+ )
1445
+
1446
+ @property
1447
+ def enable_gui(self) -> bool:
1448
+ """Enable GUI interaction."""
1449
+ return self._d["enable_gui"]
1450
+
1451
+ @enable_gui.setter
1452
+ def enable_gui(self, value: bool) -> None:
1453
+ self._set_boolean("enable_gui", value)
1454
+
1455
+ @property
1456
+ def gui_location(self) -> tuple[Any]:
1457
+ """Location parameters for the GUI window (e.g., screen coordinates or layout settings)."""
1458
+ return self._d["gui_location"]
1309
1459
 
1310
- enable_gui = property(
1311
- lambda self: self._d["enable_gui"],
1312
- lambda self, val: self._set_boolean("enable_gui", val),
1313
- doc="Enable GUI interaction.",
1314
- )
1315
-
1316
- gui_location = property(
1317
- lambda self: self._d["gui_location"],
1318
- lambda self, val: self._set_tuple("gui_location", val),
1319
- doc="Enable GUI interaction.",
1320
- )
1321
-
1322
- fullscreen = property(
1323
- lambda self: self._d["fullscreen"],
1324
- lambda self, val: self._set_boolean("fullscreen", val),
1325
- doc="Expand the window to its maximum possible size.",
1326
- )
1327
-
1328
- use_projection_fill_shaders = property(
1329
- lambda self: self._d["use_projection_fill_shaders"],
1330
- lambda self, val: self._set_boolean("use_projection_fill_shaders", val),
1331
- doc="Use shaders for OpenGLVMobject fill which are compatible with transformation matrices.",
1332
- )
1333
-
1334
- use_projection_stroke_shaders = property(
1335
- lambda self: self._d["use_projection_stroke_shaders"],
1336
- lambda self, val: self._set_boolean("use_projection_stroke_shaders", val),
1337
- doc="Use shaders for OpenGLVMobject stroke which are compatible with transformation matrices.",
1338
- )
1339
-
1340
- zero_pad = property(
1341
- lambda self: self._d["zero_pad"],
1342
- lambda self, val: self._set_int_between("zero_pad", val, 0, 9),
1343
- doc="PNG zero padding. A number between 0 (no zero padding) and 9 (9 columns minimum).",
1344
- )
1345
-
1346
- def get_dir(self, key: str, **kwargs: str) -> Path:
1460
+ @gui_location.setter
1461
+ def gui_location(self, value: tuple[Any]) -> None:
1462
+ self._set_tuple("gui_location", value)
1463
+
1464
+ @property
1465
+ def fullscreen(self) -> bool:
1466
+ """Expand the window to its maximum possible size."""
1467
+ return self._d["fullscreen"]
1468
+
1469
+ @fullscreen.setter
1470
+ def fullscreen(self, value: bool) -> None:
1471
+ self._set_boolean("fullscreen", value)
1472
+
1473
+ @property
1474
+ def use_projection_fill_shaders(self) -> bool:
1475
+ """Use shaders for OpenGLVMobject fill which are compatible with transformation matrices."""
1476
+ return self._d["use_projection_fill_shaders"]
1477
+
1478
+ @use_projection_fill_shaders.setter
1479
+ def use_projection_fill_shaders(self, value: bool) -> None:
1480
+ self._set_boolean("use_projection_fill_shaders", value)
1481
+
1482
+ @property
1483
+ def use_projection_stroke_shaders(self) -> bool:
1484
+ """Use shaders for OpenGLVMobject stroke which are compatible with transformation matrices."""
1485
+ return self._d["use_projection_stroke_shaders"]
1486
+
1487
+ @use_projection_stroke_shaders.setter
1488
+ def use_projection_stroke_shaders(self, value: bool) -> None:
1489
+ self._set_boolean("use_projection_stroke_shaders", value)
1490
+
1491
+ @property
1492
+ def zero_pad(self) -> int:
1493
+ """PNG zero padding. A number between 0 (no zero padding) and 9 (9 columns minimum)."""
1494
+ return self._d["zero_pad"]
1495
+
1496
+ @zero_pad.setter
1497
+ def zero_pad(self, value: int) -> None:
1498
+ self._set_int_between("zero_pad", value, 0, 9)
1499
+
1500
+ def get_dir(self, key: str, **kwargs: Any) -> Path:
1347
1501
  """Resolve a config option that stores a directory.
1348
1502
 
1349
1503
  Config options that store directories may depend on one another. This
@@ -1494,110 +1648,146 @@ class ManimConfig(MutableMapping):
1494
1648
  ) from exc
1495
1649
  return Path(path) if path else None
1496
1650
 
1497
- def _set_dir(self, key: str, val: str | Path):
1651
+ def _set_dir(self, key: str, val: str | Path) -> None:
1498
1652
  if isinstance(val, Path):
1499
1653
  self._d.__setitem__(key, str(val))
1500
1654
  else:
1501
1655
  self._d.__setitem__(key, val)
1502
1656
 
1503
- assets_dir = property(
1504
- lambda self: self._d["assets_dir"],
1505
- lambda self, val: self._set_dir("assets_dir", val),
1506
- doc="Directory to locate video assets (no flag).",
1507
- )
1508
-
1509
- log_dir = property(
1510
- lambda self: self._d["log_dir"],
1511
- lambda self, val: self._set_dir("log_dir", val),
1512
- doc="Directory to place logs. See :meth:`ManimConfig.get_dir`.",
1513
- )
1514
-
1515
- video_dir = property(
1516
- lambda self: self._d["video_dir"],
1517
- lambda self, val: self._set_dir("video_dir", val),
1518
- doc="Directory to place videos (no flag). See :meth:`ManimConfig.get_dir`.",
1519
- )
1520
-
1521
- sections_dir = property(
1522
- lambda self: self._d["sections_dir"],
1523
- lambda self, val: self._set_dir("sections_dir", val),
1524
- doc="Directory to place section videos (no flag). See :meth:`ManimConfig.get_dir`.",
1525
- )
1526
-
1527
- images_dir = property(
1528
- lambda self: self._d["images_dir"],
1529
- lambda self, val: self._set_dir("images_dir", val),
1530
- doc="Directory to place images (no flag). See :meth:`ManimConfig.get_dir`.",
1531
- )
1532
-
1533
- text_dir = property(
1534
- lambda self: self._d["text_dir"],
1535
- lambda self, val: self._set_dir("text_dir", val),
1536
- doc="Directory to place text (no flag). See :meth:`ManimConfig.get_dir`.",
1537
- )
1538
-
1539
- tex_dir = property(
1540
- lambda self: self._d["tex_dir"],
1541
- lambda self, val: self._set_dir("tex_dir", val),
1542
- doc="Directory to place tex (no flag). See :meth:`ManimConfig.get_dir`.",
1543
- )
1544
-
1545
- partial_movie_dir = property(
1546
- lambda self: self._d["partial_movie_dir"],
1547
- lambda self, val: self._set_dir("partial_movie_dir", val),
1548
- doc="Directory to place partial movie files (no flag). See :meth:`ManimConfig.get_dir`.",
1549
- )
1550
-
1551
- custom_folders = property(
1552
- lambda self: self._d["custom_folders"],
1553
- lambda self, val: self._set_boolean("custom_folders", val),
1554
- doc="Whether to use custom folder output.",
1555
- )
1556
-
1557
- input_file = property(
1558
- lambda self: self._d["input_file"],
1559
- lambda self, val: self._set_dir("input_file", val),
1560
- doc="Input file name.",
1561
- )
1562
-
1563
- output_file = property(
1564
- lambda self: self._d["output_file"],
1565
- lambda self, val: self._set_dir("output_file", val),
1566
- doc="Output file name (-o).",
1567
- )
1568
-
1569
- scene_names = property(
1570
- lambda self: self._d["scene_names"],
1571
- lambda self, val: self._d.__setitem__("scene_names", val),
1572
- doc="Scenes to play from file.",
1573
- )
1574
-
1575
- @property
1576
- def tex_template(self):
1657
+ @property
1658
+ def assets_dir(self) -> str:
1659
+ """Directory to locate video assets (no flag)."""
1660
+ return self._d["assets_dir"]
1661
+
1662
+ @assets_dir.setter
1663
+ def assets_dir(self, value: str | Path) -> None:
1664
+ self._set_dir("assets_dir", value)
1665
+
1666
+ @property
1667
+ def log_dir(self) -> str:
1668
+ """Directory to place logs. See :meth:`ManimConfig.get_dir`."""
1669
+ return self._d["log_dir"]
1670
+
1671
+ @log_dir.setter
1672
+ def log_dir(self, value: str | Path) -> None:
1673
+ self._set_dir("log_dir", value)
1674
+
1675
+ @property
1676
+ def video_dir(self) -> str:
1677
+ """Directory to place videos (no flag). See :meth:`ManimConfig.get_dir`."""
1678
+ return self._d["video_dir"]
1679
+
1680
+ @video_dir.setter
1681
+ def video_dir(self, value: str | Path) -> None:
1682
+ self._set_dir("video_dir", value)
1683
+
1684
+ @property
1685
+ def sections_dir(self) -> str:
1686
+ """Directory to place section videos (no flag). See :meth:`ManimConfig.get_dir`."""
1687
+ return self._d["sections_dir"]
1688
+
1689
+ @sections_dir.setter
1690
+ def sections_dir(self, value: str | Path) -> None:
1691
+ self._set_dir("sections_dir", value)
1692
+
1693
+ @property
1694
+ def images_dir(self) -> str:
1695
+ """Directory to place images (no flag). See :meth:`ManimConfig.get_dir`."""
1696
+ return self._d["images_dir"]
1697
+
1698
+ @images_dir.setter
1699
+ def images_dir(self, value: str | Path) -> None:
1700
+ self._set_dir("images_dir", value)
1701
+
1702
+ @property
1703
+ def text_dir(self) -> str:
1704
+ """Directory to place text (no flag). See :meth:`ManimConfig.get_dir`."""
1705
+ return self._d["text_dir"]
1706
+
1707
+ @text_dir.setter
1708
+ def text_dir(self, value: str | Path) -> None:
1709
+ self._set_dir("text_dir", value)
1710
+
1711
+ @property
1712
+ def tex_dir(self) -> str:
1713
+ """Directory to place tex (no flag). See :meth:`ManimConfig.get_dir`."""
1714
+ return self._d["tex_dir"]
1715
+
1716
+ @tex_dir.setter
1717
+ def tex_dir(self, value: str | Path) -> None:
1718
+ self._set_dir("tex_dir", value)
1719
+
1720
+ @property
1721
+ def partial_movie_dir(self) -> str:
1722
+ """Directory to place partial movie files (no flag). See :meth:`ManimConfig.get_dir`."""
1723
+ return self._d["partial_movie_dir"]
1724
+
1725
+ @partial_movie_dir.setter
1726
+ def partial_movie_dir(self, value: str | Path) -> None:
1727
+ self._set_dir("partial_movie_dir", value)
1728
+
1729
+ @property
1730
+ def custom_folders(self) -> str:
1731
+ """Whether to use custom folder output."""
1732
+ return self._d["custom_folders"]
1733
+
1734
+ @custom_folders.setter
1735
+ def custom_folders(self, value: str | Path) -> None:
1736
+ self._set_dir("custom_folders", value)
1737
+
1738
+ @property
1739
+ def input_file(self) -> str:
1740
+ """Input file name."""
1741
+ return self._d["input_file"]
1742
+
1743
+ @input_file.setter
1744
+ def input_file(self, value: str | Path) -> None:
1745
+ self._set_dir("input_file", value)
1746
+
1747
+ @property
1748
+ def output_file(self) -> str:
1749
+ """Output file name (-o)."""
1750
+ return self._d["output_file"]
1751
+
1752
+ @output_file.setter
1753
+ def output_file(self, value: str | Path) -> None:
1754
+ self._set_dir("output_file", value)
1755
+
1756
+ @property
1757
+ def scene_names(self) -> list[str]:
1758
+ """Scenes to play from file."""
1759
+ return self._d["scene_names"]
1760
+
1761
+ @scene_names.setter
1762
+ def scene_names(self, value: list[str]) -> None:
1763
+ self._d.__setitem__("scene_names", value)
1764
+
1765
+ @property
1766
+ def tex_template(self) -> TexTemplate:
1577
1767
  """Template used when rendering Tex. See :class:`.TexTemplate`."""
1578
1768
  if not hasattr(self, "_tex_template") or not self._tex_template:
1579
1769
  fn = self._d["tex_template_file"]
1580
1770
  if fn:
1581
- self._tex_template = TexTemplateFromFile(tex_filename=fn)
1771
+ self._tex_template = TexTemplate.from_file(fn)
1582
1772
  else:
1583
1773
  self._tex_template = TexTemplate()
1584
1774
  return self._tex_template
1585
1775
 
1586
1776
  @tex_template.setter
1587
- def tex_template(self, val: TexTemplateFromFile | TexTemplate) -> None:
1588
- if isinstance(val, (TexTemplateFromFile, TexTemplate)):
1777
+ def tex_template(self, val: TexTemplate) -> None:
1778
+ if isinstance(val, TexTemplate):
1589
1779
  self._tex_template = val
1590
1780
 
1591
1781
  @property
1592
- def tex_template_file(self):
1593
- """File to read Tex template from (no flag). See :class:`.TexTemplateFromFile`."""
1782
+ def tex_template_file(self) -> Path:
1783
+ """File to read Tex template from (no flag). See :class:`.TexTemplate`."""
1594
1784
  return self._d["tex_template_file"]
1595
1785
 
1596
1786
  @tex_template_file.setter
1597
1787
  def tex_template_file(self, val: str) -> None:
1598
1788
  if val:
1599
1789
  if not os.access(val, os.R_OK):
1600
- logging.getLogger("manim").warning(
1790
+ logger.warning(
1601
1791
  f"Custom TeX template {val} not found or not readable.",
1602
1792
  )
1603
1793
  else:
@@ -1606,17 +1796,19 @@ class ManimConfig(MutableMapping):
1606
1796
  self._d["tex_template_file"] = val # actually set the falsy value
1607
1797
 
1608
1798
  @property
1609
- def plugins(self):
1799
+ def plugins(self) -> list[str]:
1610
1800
  """List of plugins to enable."""
1611
1801
  return self._d["plugins"]
1612
1802
 
1613
1803
  @plugins.setter
1614
- def plugins(self, value):
1804
+ def plugins(self, value: list[str]):
1615
1805
  self._d["plugins"] = value
1616
1806
 
1617
1807
 
1808
+ # TODO: to be used in the future - see PR #620
1809
+ # https://github.com/ManimCommunity/manim/pull/620
1618
1810
  class ManimFrame(Mapping):
1619
- _OPTS: set[str] = {
1811
+ _OPTS: ClassVar[set[str]] = {
1620
1812
  "pixel_width",
1621
1813
  "pixel_height",
1622
1814
  "aspect_ratio",
@@ -1629,7 +1821,7 @@ class ManimFrame(Mapping):
1629
1821
  "left_side",
1630
1822
  "right_side",
1631
1823
  }
1632
- _CONSTANTS: dict[str, np.ndarray] = {
1824
+ _CONSTANTS: ClassVar[dict[str, Vector3D]] = {
1633
1825
  "UP": np.array((0.0, 1.0, 0.0)),
1634
1826
  "DOWN": np.array((0.0, -1.0, 0.0)),
1635
1827
  "RIGHT": np.array((1.0, 0.0, 0.0)),
@@ -1646,6 +1838,8 @@ class ManimFrame(Mapping):
1646
1838
  "DR": np.array((1.0, -1.0, 0.0)),
1647
1839
  }
1648
1840
 
1841
+ _c: ManimConfig
1842
+
1649
1843
  def __init__(self, c: ManimConfig) -> None:
1650
1844
  if not isinstance(c, ManimConfig):
1651
1845
  raise TypeError("argument must be instance of 'ManimConfig'")
@@ -1662,20 +1856,20 @@ class ManimFrame(Mapping):
1662
1856
  else:
1663
1857
  raise KeyError(key)
1664
1858
 
1665
- def __iter__(self) -> Iterable:
1859
+ def __iter__(self) -> Iterable[str]:
1666
1860
  return iter(list(self._OPTS) + list(self._CONSTANTS))
1667
1861
 
1668
1862
  def __len__(self) -> int:
1669
1863
  return len(self._OPTS)
1670
1864
 
1671
1865
  # make this truly immutable
1672
- def __setattr__(self, attr, val) -> None:
1866
+ def __setattr__(self, attr: Any, val: Any) -> NoReturn:
1673
1867
  raise TypeError("'ManimFrame' object does not support item assignment")
1674
1868
 
1675
- def __setitem__(self, key, val) -> None:
1869
+ def __setitem__(self, key: Any, val: Any) -> NoReturn:
1676
1870
  raise TypeError("'ManimFrame' object does not support item assignment")
1677
1871
 
1678
- def __delitem__(self, key) -> None:
1872
+ def __delitem__(self, key: Any) -> NoReturn:
1679
1873
  raise TypeError("'ManimFrame' object does not support item deletion")
1680
1874
 
1681
1875