manim 0.18.1__py3-none-any.whl → 0.19.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of manim might be problematic. Click here for more details.
- manim/__main__.py +45 -12
- manim/_config/__init__.py +2 -2
- manim/_config/cli_colors.py +8 -4
- manim/_config/default.cfg +0 -2
- manim/_config/logger_utils.py +5 -0
- manim/_config/utils.py +29 -38
- manim/animation/animation.py +148 -8
- manim/animation/composition.py +16 -13
- manim/animation/creation.py +184 -8
- manim/animation/fading.py +5 -8
- manim/animation/indication.py +93 -26
- manim/animation/movement.py +21 -3
- manim/animation/rotation.py +2 -1
- manim/animation/specialized.py +3 -5
- manim/animation/speedmodifier.py +3 -3
- manim/animation/transform.py +4 -5
- manim/animation/updaters/mobject_update_utils.py +17 -14
- manim/camera/camera.py +2 -2
- manim/cli/__init__.py +17 -0
- manim/cli/cfg/group.py +52 -36
- manim/cli/checkhealth/checks.py +92 -76
- manim/cli/checkhealth/commands.py +12 -5
- manim/cli/default_group.py +148 -24
- manim/cli/init/commands.py +28 -23
- manim/cli/plugins/commands.py +13 -3
- manim/cli/render/commands.py +47 -42
- manim/cli/render/global_options.py +43 -9
- manim/cli/render/render_options.py +84 -19
- manim/constants.py +11 -4
- manim/mobject/frame.py +0 -1
- manim/mobject/geometry/arc.py +109 -75
- manim/mobject/geometry/boolean_ops.py +20 -17
- manim/mobject/geometry/labeled.py +300 -77
- manim/mobject/geometry/line.py +120 -60
- manim/mobject/geometry/polygram.py +109 -25
- manim/mobject/geometry/shape_matchers.py +35 -15
- manim/mobject/geometry/tips.py +36 -27
- manim/mobject/graph.py +48 -40
- manim/mobject/graphing/coordinate_systems.py +110 -45
- manim/mobject/graphing/functions.py +16 -10
- manim/mobject/graphing/number_line.py +23 -9
- manim/mobject/graphing/probability.py +2 -10
- manim/mobject/graphing/scale.py +6 -5
- manim/mobject/matrix.py +17 -19
- manim/mobject/mobject.py +149 -103
- manim/mobject/opengl/opengl_geometry.py +4 -8
- manim/mobject/opengl/opengl_mobject.py +506 -343
- manim/mobject/opengl/opengl_point_cloud_mobject.py +3 -7
- manim/mobject/opengl/opengl_surface.py +1 -2
- manim/mobject/opengl/opengl_vectorized_mobject.py +27 -65
- manim/mobject/svg/brace.py +61 -13
- manim/mobject/svg/svg_mobject.py +2 -1
- manim/mobject/table.py +11 -12
- manim/mobject/text/code_mobject.py +186 -550
- manim/mobject/text/numbers.py +7 -7
- manim/mobject/text/tex_mobject.py +22 -13
- manim/mobject/text/text_mobject.py +29 -20
- manim/mobject/three_d/polyhedra.py +98 -1
- manim/mobject/three_d/three_dimensions.py +59 -31
- manim/mobject/types/image_mobject.py +37 -23
- manim/mobject/types/point_cloud_mobject.py +103 -67
- manim/mobject/types/vectorized_mobject.py +387 -214
- manim/mobject/value_tracker.py +2 -1
- manim/mobject/vector_field.py +2 -4
- manim/opengl/__init__.py +3 -3
- manim/plugins/__init__.py +2 -3
- manim/plugins/plugins_flags.py +3 -3
- manim/renderer/cairo_renderer.py +11 -11
- manim/renderer/opengl_renderer.py +19 -20
- manim/renderer/shader.py +2 -3
- manim/renderer/shader_wrapper.py +3 -2
- manim/scene/moving_camera_scene.py +23 -0
- manim/scene/scene.py +72 -41
- manim/scene/scene_file_writer.py +313 -164
- manim/scene/section.py +15 -15
- manim/scene/three_d_scene.py +8 -15
- manim/scene/vector_space_scene.py +3 -6
- manim/typing.py +326 -66
- manim/utils/bezier.py +1658 -381
- manim/utils/caching.py +11 -5
- manim/utils/color/AS2700.py +2 -0
- manim/utils/color/BS381.py +2 -0
- manim/utils/color/DVIPSNAMES.py +96 -0
- manim/utils/color/SVGNAMES.py +179 -0
- manim/utils/color/X11.py +3 -0
- manim/utils/color/XKCD.py +2 -0
- manim/utils/color/__init__.py +8 -5
- manim/utils/color/core.py +818 -301
- manim/utils/color/manim_colors.py +7 -9
- manim/utils/commands.py +40 -19
- manim/utils/config_ops.py +18 -13
- manim/utils/debug.py +8 -6
- manim/utils/deprecation.py +92 -43
- manim/utils/docbuild/autoaliasattr_directive.py +45 -8
- manim/utils/docbuild/autocolor_directive.py +12 -13
- manim/utils/docbuild/manim_directive.py +35 -29
- manim/utils/docbuild/module_parsing.py +74 -27
- manim/utils/family.py +3 -3
- manim/utils/family_ops.py +12 -4
- manim/utils/file_ops.py +22 -16
- manim/utils/hashing.py +7 -7
- manim/utils/images.py +10 -4
- manim/utils/ipython_magic.py +12 -8
- manim/utils/iterables.py +161 -119
- manim/utils/module_ops.py +55 -19
- manim/utils/opengl.py +68 -23
- manim/utils/parameter_parsing.py +3 -2
- manim/utils/paths.py +11 -5
- manim/utils/polylabel.py +168 -0
- manim/utils/qhull.py +218 -0
- manim/utils/rate_functions.py +69 -32
- manim/utils/simple_functions.py +24 -15
- manim/utils/sounds.py +7 -1
- manim/utils/space_ops.py +48 -37
- manim/utils/testing/_frames_testers.py +13 -8
- manim/utils/testing/_show_diff.py +5 -3
- manim/utils/testing/_test_class_makers.py +33 -18
- manim/utils/testing/frames_comparison.py +20 -14
- manim/utils/tex.py +4 -2
- manim/utils/tex_file_writing.py +45 -45
- manim/utils/tex_templates.py +1 -1
- manim/utils/unit.py +6 -5
- {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/METADATA +16 -9
- manim-0.19.0.dist-info/RECORD +221 -0
- {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/WHEEL +1 -1
- manim-0.18.1.dist-info/RECORD +0 -217
- {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/LICENSE +0 -0
- {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/LICENSE.community +0 -0
- {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/entry_points.txt +0 -0
manim/__main__.py
CHANGED
|
@@ -3,22 +3,49 @@ from __future__ import annotations
|
|
|
3
3
|
import click
|
|
4
4
|
import cloup
|
|
5
5
|
|
|
6
|
-
from
|
|
7
|
-
from .
|
|
8
|
-
from .cli.
|
|
9
|
-
from .cli.
|
|
10
|
-
from .cli.
|
|
11
|
-
from .cli.
|
|
12
|
-
from .cli.
|
|
13
|
-
from .
|
|
6
|
+
from manim import __version__
|
|
7
|
+
from manim._config import cli_ctx_settings, console
|
|
8
|
+
from manim.cli.cfg.group import cfg
|
|
9
|
+
from manim.cli.checkhealth.commands import checkhealth
|
|
10
|
+
from manim.cli.default_group import DefaultGroup
|
|
11
|
+
from manim.cli.init.commands import init
|
|
12
|
+
from manim.cli.plugins.commands import plugins
|
|
13
|
+
from manim.cli.render.commands import render
|
|
14
|
+
from manim.constants import EPILOG
|
|
14
15
|
|
|
15
16
|
|
|
16
|
-
def show_splash(ctx, param, value):
|
|
17
|
+
def show_splash(ctx: click.Context, param: click.Option, value: str | None) -> None:
|
|
18
|
+
"""When giving a value by console, show an initial message with the Manim
|
|
19
|
+
version before executing any other command: ``Manim Community vA.B.C``.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
ctx
|
|
24
|
+
The Click context.
|
|
25
|
+
param
|
|
26
|
+
A Click option.
|
|
27
|
+
value
|
|
28
|
+
A string value given by console, or None.
|
|
29
|
+
"""
|
|
17
30
|
if value:
|
|
18
31
|
console.print(f"Manim Community [green]v{__version__}[/green]\n")
|
|
19
32
|
|
|
20
33
|
|
|
21
|
-
def print_version_and_exit(
|
|
34
|
+
def print_version_and_exit(
|
|
35
|
+
ctx: click.Context, param: click.Option, value: str | None
|
|
36
|
+
) -> None:
|
|
37
|
+
"""Same as :func:`show_splash`, but also exit when giving a value by
|
|
38
|
+
console.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
ctx
|
|
43
|
+
The Click context.
|
|
44
|
+
param
|
|
45
|
+
A Click option.
|
|
46
|
+
value
|
|
47
|
+
A string value given by console, or None.
|
|
48
|
+
"""
|
|
22
49
|
show_splash(ctx, param, value)
|
|
23
50
|
if value:
|
|
24
51
|
ctx.exit()
|
|
@@ -53,8 +80,14 @@ def print_version_and_exit(ctx, param, value):
|
|
|
53
80
|
expose_value=False,
|
|
54
81
|
)
|
|
55
82
|
@cloup.pass_context
|
|
56
|
-
def main(ctx):
|
|
57
|
-
"""The entry point for
|
|
83
|
+
def main(ctx: click.Context) -> None:
|
|
84
|
+
"""The entry point for Manim.
|
|
85
|
+
|
|
86
|
+
Parameters
|
|
87
|
+
----------
|
|
88
|
+
ctx
|
|
89
|
+
The Click context.
|
|
90
|
+
"""
|
|
58
91
|
pass
|
|
59
92
|
|
|
60
93
|
|
manim/_config/__init__.py
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
|
+
from collections.abc import Generator
|
|
6
7
|
from contextlib import contextmanager
|
|
7
|
-
from typing import Any
|
|
8
|
+
from typing import Any
|
|
8
9
|
|
|
9
10
|
from .cli_colors import parse_cli_ctx
|
|
10
11
|
from .logger_utils import make_logger
|
|
@@ -67,7 +68,6 @@ def tempconfig(temp: ManimConfig | dict[str, Any]) -> Generator[None, None, None
|
|
|
67
68
|
8.0
|
|
68
69
|
>>> with tempconfig({"frame_height": 100.0}):
|
|
69
70
|
... print(config["frame_height"])
|
|
70
|
-
...
|
|
71
71
|
100.0
|
|
72
72
|
>>> config["frame_height"]
|
|
73
73
|
8.0
|
manim/_config/cli_colors.py
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import configparser
|
|
4
|
+
from typing import Any
|
|
4
5
|
|
|
5
6
|
from cloup import Context, HelpFormatter, HelpTheme, Style
|
|
6
7
|
|
|
7
8
|
__all__ = ["parse_cli_ctx"]
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
def parse_cli_ctx(parser: configparser.SectionProxy) ->
|
|
11
|
+
def parse_cli_ctx(parser: configparser.SectionProxy) -> dict[str, Any]:
|
|
11
12
|
formatter_settings: dict[str, str | int] = {
|
|
12
13
|
"indent_increment": int(parser["indent_increment"]),
|
|
13
14
|
"width": int(parser["width"]),
|
|
@@ -35,15 +36,18 @@ def parse_cli_ctx(parser: configparser.SectionProxy) -> Context:
|
|
|
35
36
|
theme = parser["theme"] if parser["theme"] else None
|
|
36
37
|
if theme is None:
|
|
37
38
|
formatter = HelpFormatter.settings(
|
|
38
|
-
theme=HelpTheme(**theme_settings),
|
|
39
|
+
theme=HelpTheme(**theme_settings),
|
|
40
|
+
**formatter_settings, # type: ignore[arg-type]
|
|
39
41
|
)
|
|
40
42
|
elif theme.lower() == "dark":
|
|
41
43
|
formatter = HelpFormatter.settings(
|
|
42
|
-
theme=HelpTheme.dark().with_(**theme_settings),
|
|
44
|
+
theme=HelpTheme.dark().with_(**theme_settings),
|
|
45
|
+
**formatter_settings, # type: ignore[arg-type]
|
|
43
46
|
)
|
|
44
47
|
elif theme.lower() == "light":
|
|
45
48
|
formatter = HelpFormatter.settings(
|
|
46
|
-
theme=HelpTheme.light().with_(**theme_settings),
|
|
49
|
+
theme=HelpTheme.light().with_(**theme_settings),
|
|
50
|
+
**formatter_settings, # type: ignore[arg-type]
|
|
47
51
|
)
|
|
48
52
|
|
|
49
53
|
return Context.settings(
|
manim/_config/default.cfg
CHANGED
|
@@ -221,8 +221,6 @@ repr_number = green
|
|
|
221
221
|
# Uncomment the following line to manually set the loglevel for ffmpeg. See
|
|
222
222
|
# ffmpeg manpage for accepted values
|
|
223
223
|
loglevel = ERROR
|
|
224
|
-
# defaults to the one present in path
|
|
225
|
-
ffmpeg_executable = ffmpeg
|
|
226
224
|
|
|
227
225
|
[jupyter]
|
|
228
226
|
media_embed = False
|
manim/_config/logger_utils.py
CHANGED
|
@@ -99,6 +99,11 @@ def make_logger(
|
|
|
99
99
|
logger = logging.getLogger("manim")
|
|
100
100
|
logger.addHandler(rich_handler)
|
|
101
101
|
logger.setLevel(verbosity)
|
|
102
|
+
logger.propagate = False
|
|
103
|
+
|
|
104
|
+
if not (libav_logger := logging.getLogger()).hasHandlers():
|
|
105
|
+
libav_logger.addHandler(rich_handler)
|
|
106
|
+
libav_logger.setLevel(verbosity)
|
|
102
107
|
|
|
103
108
|
return logger, console, error_console
|
|
104
109
|
|
manim/_config/utils.py
CHANGED
|
@@ -20,9 +20,9 @@ import logging
|
|
|
20
20
|
import os
|
|
21
21
|
import re
|
|
22
22
|
import sys
|
|
23
|
-
from collections.abc import Mapping, MutableMapping
|
|
23
|
+
from collections.abc import Iterable, Iterator, Mapping, MutableMapping
|
|
24
24
|
from pathlib import Path
|
|
25
|
-
from typing import TYPE_CHECKING, Any, ClassVar,
|
|
25
|
+
from typing import TYPE_CHECKING, Any, ClassVar, NoReturn
|
|
26
26
|
|
|
27
27
|
import numpy as np
|
|
28
28
|
|
|
@@ -40,6 +40,8 @@ if TYPE_CHECKING:
|
|
|
40
40
|
|
|
41
41
|
__all__ = ["config_file_paths", "make_config_parser", "ManimConfig", "ManimFrame"]
|
|
42
42
|
|
|
43
|
+
logger = logging.getLogger("manim")
|
|
44
|
+
|
|
43
45
|
|
|
44
46
|
def config_file_paths() -> list[Path]:
|
|
45
47
|
"""The paths where ``.cfg`` files will be searched for.
|
|
@@ -263,7 +265,6 @@ class ManimConfig(MutableMapping):
|
|
|
263
265
|
"dry_run",
|
|
264
266
|
"enable_wireframe",
|
|
265
267
|
"ffmpeg_loglevel",
|
|
266
|
-
"ffmpeg_executable",
|
|
267
268
|
"format",
|
|
268
269
|
"flush_cache",
|
|
269
270
|
"frame_height",
|
|
@@ -322,7 +323,7 @@ class ManimConfig(MutableMapping):
|
|
|
322
323
|
}
|
|
323
324
|
|
|
324
325
|
def __init__(self) -> None:
|
|
325
|
-
self._d: dict[str, Any | None] =
|
|
326
|
+
self._d: dict[str, Any | None] = dict.fromkeys(self._OPTS)
|
|
326
327
|
|
|
327
328
|
# behave like a dict
|
|
328
329
|
def __iter__(self) -> Iterator[str]:
|
|
@@ -371,7 +372,6 @@ class ManimConfig(MutableMapping):
|
|
|
371
372
|
:meth:`~ManimConfig.digest_parser`
|
|
372
373
|
|
|
373
374
|
"""
|
|
374
|
-
|
|
375
375
|
if isinstance(obj, ManimConfig):
|
|
376
376
|
self._d.update(obj._d)
|
|
377
377
|
if obj.tex_template:
|
|
@@ -642,21 +642,18 @@ class ManimConfig(MutableMapping):
|
|
|
642
642
|
gui_location = tuple(
|
|
643
643
|
map(int, re.split(r"[;,\-]", parser["CLI"]["gui_location"])),
|
|
644
644
|
)
|
|
645
|
-
|
|
645
|
+
self.gui_location = gui_location
|
|
646
646
|
|
|
647
647
|
window_size = parser["CLI"][
|
|
648
648
|
"window_size"
|
|
649
649
|
] # if not "default", get a tuple of the position
|
|
650
650
|
if window_size != "default":
|
|
651
651
|
window_size = tuple(map(int, re.split(r"[;,\-]", window_size)))
|
|
652
|
-
|
|
652
|
+
self.window_size = window_size
|
|
653
653
|
|
|
654
654
|
# plugins
|
|
655
655
|
plugins = parser["CLI"].get("plugins", fallback="", raw=True)
|
|
656
|
-
if plugins == ""
|
|
657
|
-
plugins = []
|
|
658
|
-
else:
|
|
659
|
-
plugins = plugins.split(",")
|
|
656
|
+
plugins = [] if plugins == "" else plugins.split(",")
|
|
660
657
|
self.plugins = plugins
|
|
661
658
|
# the next two must be set AFTER digesting pixel_width and pixel_height
|
|
662
659
|
self["frame_height"] = parser["CLI"].getfloat("frame_height", 8.0)
|
|
@@ -673,25 +670,21 @@ class ManimConfig(MutableMapping):
|
|
|
673
670
|
|
|
674
671
|
val = parser["CLI"].get("progress_bar")
|
|
675
672
|
if val:
|
|
676
|
-
|
|
673
|
+
self.progress_bar = val
|
|
677
674
|
|
|
678
675
|
val = parser["ffmpeg"].get("loglevel")
|
|
679
676
|
if val:
|
|
680
677
|
self.ffmpeg_loglevel = val
|
|
681
678
|
|
|
682
|
-
# TODO: Fix the mess above and below
|
|
683
|
-
val = parser["ffmpeg"].get("ffmpeg_executable")
|
|
684
|
-
setattr(self, "ffmpeg_executable", val)
|
|
685
|
-
|
|
686
679
|
try:
|
|
687
680
|
val = parser["jupyter"].getboolean("media_embed")
|
|
688
681
|
except ValueError:
|
|
689
682
|
val = None
|
|
690
|
-
|
|
683
|
+
self.media_embed = val
|
|
691
684
|
|
|
692
685
|
val = parser["jupyter"].get("media_width")
|
|
693
686
|
if val:
|
|
694
|
-
|
|
687
|
+
self.media_width = val
|
|
695
688
|
|
|
696
689
|
val = parser["CLI"].get("quality", fallback="", raw=True)
|
|
697
690
|
if val:
|
|
@@ -804,7 +797,7 @@ class ManimConfig(MutableMapping):
|
|
|
804
797
|
try:
|
|
805
798
|
self.upto_animation_number = nflag[1]
|
|
806
799
|
except Exception:
|
|
807
|
-
|
|
800
|
+
logger.info(
|
|
808
801
|
f"No end scene number specified in -n option. Rendering from {nflag[0]} onwards...",
|
|
809
802
|
)
|
|
810
803
|
|
|
@@ -842,15 +835,12 @@ class ManimConfig(MutableMapping):
|
|
|
842
835
|
if args.tex_template:
|
|
843
836
|
self.tex_template = TexTemplate.from_file(args.tex_template)
|
|
844
837
|
|
|
845
|
-
if
|
|
846
|
-
self.renderer == RendererType.OPENGL
|
|
847
|
-
and getattr(args, "write_to_movie") is None
|
|
848
|
-
):
|
|
838
|
+
if self.renderer == RendererType.OPENGL and args.write_to_movie is None:
|
|
849
839
|
# --write_to_movie was not passed on the command line, so don't generate video.
|
|
850
840
|
self["write_to_movie"] = False
|
|
851
841
|
|
|
852
842
|
# Handle --gui_location flag.
|
|
853
|
-
if
|
|
843
|
+
if args.gui_location is not None:
|
|
854
844
|
self.gui_location = args.gui_location
|
|
855
845
|
|
|
856
846
|
return self
|
|
@@ -1043,7 +1033,7 @@ class ManimConfig(MutableMapping):
|
|
|
1043
1033
|
val,
|
|
1044
1034
|
["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
|
|
1045
1035
|
)
|
|
1046
|
-
|
|
1036
|
+
logger.setLevel(val)
|
|
1047
1037
|
|
|
1048
1038
|
@property
|
|
1049
1039
|
def format(self) -> str:
|
|
@@ -1057,8 +1047,9 @@ class ManimConfig(MutableMapping):
|
|
|
1057
1047
|
val,
|
|
1058
1048
|
[None, "png", "gif", "mp4", "mov", "webm"],
|
|
1059
1049
|
)
|
|
1050
|
+
self.resolve_movie_file_extension(self.transparent)
|
|
1060
1051
|
if self.format == "webm":
|
|
1061
|
-
|
|
1052
|
+
logger.warning(
|
|
1062
1053
|
"Output format set as webm, this can be slower than other formats",
|
|
1063
1054
|
)
|
|
1064
1055
|
|
|
@@ -1074,15 +1065,7 @@ class ManimConfig(MutableMapping):
|
|
|
1074
1065
|
val,
|
|
1075
1066
|
["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
|
|
1076
1067
|
)
|
|
1077
|
-
|
|
1078
|
-
@property
|
|
1079
|
-
def ffmpeg_executable(self) -> str:
|
|
1080
|
-
"""Custom path to the ffmpeg executable."""
|
|
1081
|
-
return self._d["ffmpeg_executable"]
|
|
1082
|
-
|
|
1083
|
-
@ffmpeg_executable.setter
|
|
1084
|
-
def ffmpeg_executable(self, value: str) -> None:
|
|
1085
|
-
self._set_str("ffmpeg_executable", value)
|
|
1068
|
+
logging.getLogger("libav").setLevel(self.ffmpeg_loglevel)
|
|
1086
1069
|
|
|
1087
1070
|
@property
|
|
1088
1071
|
def media_embed(self) -> bool:
|
|
@@ -1284,6 +1267,8 @@ class ManimConfig(MutableMapping):
|
|
|
1284
1267
|
@background_opacity.setter
|
|
1285
1268
|
def background_opacity(self, value: float) -> None:
|
|
1286
1269
|
self._set_between("background_opacity", value, 0, 1)
|
|
1270
|
+
if self.background_opacity < 1:
|
|
1271
|
+
self.resolve_movie_file_extension(is_transparent=True)
|
|
1287
1272
|
|
|
1288
1273
|
@property
|
|
1289
1274
|
def frame_size(self) -> tuple[int, int]:
|
|
@@ -1318,8 +1303,8 @@ class ManimConfig(MutableMapping):
|
|
|
1318
1303
|
|
|
1319
1304
|
@property
|
|
1320
1305
|
def transparent(self) -> bool:
|
|
1321
|
-
"""Whether the background opacity is
|
|
1322
|
-
return self._d["background_opacity"]
|
|
1306
|
+
"""Whether the background opacity is less than 1.0 (-t)."""
|
|
1307
|
+
return self._d["background_opacity"] < 1.0
|
|
1323
1308
|
|
|
1324
1309
|
@transparent.setter
|
|
1325
1310
|
def transparent(self, value: bool) -> None:
|
|
@@ -1437,6 +1422,7 @@ class ManimConfig(MutableMapping):
|
|
|
1437
1422
|
self._d.__setitem__("window_size", value)
|
|
1438
1423
|
|
|
1439
1424
|
def resolve_movie_file_extension(self, is_transparent: bool) -> None:
|
|
1425
|
+
prev_file_extension = self.movie_file_extension
|
|
1440
1426
|
if is_transparent:
|
|
1441
1427
|
self.movie_file_extension = ".webm" if self.format == "webm" else ".mov"
|
|
1442
1428
|
elif self.format == "webm":
|
|
@@ -1445,6 +1431,11 @@ class ManimConfig(MutableMapping):
|
|
|
1445
1431
|
self.movie_file_extension = ".mov"
|
|
1446
1432
|
else:
|
|
1447
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
|
+
)
|
|
1448
1439
|
|
|
1449
1440
|
@property
|
|
1450
1441
|
def enable_gui(self) -> bool:
|
|
@@ -1790,7 +1781,7 @@ class ManimConfig(MutableMapping):
|
|
|
1790
1781
|
def tex_template_file(self, val: str) -> None:
|
|
1791
1782
|
if val:
|
|
1792
1783
|
if not os.access(val, os.R_OK):
|
|
1793
|
-
|
|
1784
|
+
logger.warning(
|
|
1794
1785
|
f"Custom TeX template {val} not found or not readable.",
|
|
1795
1786
|
)
|
|
1796
1787
|
else:
|
manim/animation/animation.py
CHANGED
|
@@ -7,15 +7,17 @@ from manim.mobject.opengl.opengl_mobject import OpenGLMobject
|
|
|
7
7
|
from .. import config, logger
|
|
8
8
|
from ..constants import RendererType
|
|
9
9
|
from ..mobject import mobject
|
|
10
|
-
from ..mobject.mobject import Mobject
|
|
10
|
+
from ..mobject.mobject import Group, Mobject
|
|
11
11
|
from ..mobject.opengl import opengl_mobject
|
|
12
12
|
from ..utils.rate_functions import linear, smooth
|
|
13
13
|
|
|
14
|
-
__all__ = ["Animation", "Wait", "override_animation"]
|
|
14
|
+
__all__ = ["Animation", "Wait", "Add", "override_animation"]
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
from collections.abc import Iterable, Sequence
|
|
17
18
|
from copy import deepcopy
|
|
18
|
-
from
|
|
19
|
+
from functools import partialmethod
|
|
20
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
19
21
|
|
|
20
22
|
from typing_extensions import Self
|
|
21
23
|
|
|
@@ -170,6 +172,19 @@ class Animation:
|
|
|
170
172
|
),
|
|
171
173
|
)
|
|
172
174
|
|
|
175
|
+
@property
|
|
176
|
+
def run_time(self) -> float:
|
|
177
|
+
return self._run_time
|
|
178
|
+
|
|
179
|
+
@run_time.setter
|
|
180
|
+
def run_time(self, value: float) -> None:
|
|
181
|
+
if value < 0:
|
|
182
|
+
raise ValueError(
|
|
183
|
+
f"The run_time of {self.__class__.__name__} cannot be "
|
|
184
|
+
f"negative. The given value was {value}."
|
|
185
|
+
)
|
|
186
|
+
self._run_time = value
|
|
187
|
+
|
|
173
188
|
def _typecheck_input(self, mobject: Mobject | None) -> None:
|
|
174
189
|
if mobject is None:
|
|
175
190
|
logger.debug("Animation with empty mobject")
|
|
@@ -192,11 +207,6 @@ class Animation:
|
|
|
192
207
|
method.
|
|
193
208
|
|
|
194
209
|
"""
|
|
195
|
-
if self.run_time <= 0:
|
|
196
|
-
raise ValueError(
|
|
197
|
-
f"{self} has a run_time of <= 0 seconds, this cannot be rendered correctly. "
|
|
198
|
-
"Please set the run_time to be positive"
|
|
199
|
-
)
|
|
200
210
|
self.starting_mobject = self.create_starting_mobject()
|
|
201
211
|
if self.suspend_mobject_updating:
|
|
202
212
|
# All calls to self.mobject's internal updaters
|
|
@@ -483,6 +493,52 @@ class Animation:
|
|
|
483
493
|
"""
|
|
484
494
|
return self.introducer
|
|
485
495
|
|
|
496
|
+
@classmethod
|
|
497
|
+
def __init_subclass__(cls, **kwargs) -> None:
|
|
498
|
+
super().__init_subclass__(**kwargs)
|
|
499
|
+
|
|
500
|
+
cls._original__init__ = cls.__init__
|
|
501
|
+
|
|
502
|
+
@classmethod
|
|
503
|
+
def set_default(cls, **kwargs) -> None:
|
|
504
|
+
"""Sets the default values of keyword arguments.
|
|
505
|
+
|
|
506
|
+
If this method is called without any additional keyword
|
|
507
|
+
arguments, the original default values of the initialization
|
|
508
|
+
method of this class are restored.
|
|
509
|
+
|
|
510
|
+
Parameters
|
|
511
|
+
----------
|
|
512
|
+
|
|
513
|
+
kwargs
|
|
514
|
+
Passing any keyword argument will update the default
|
|
515
|
+
values of the keyword arguments of the initialization
|
|
516
|
+
function of this class.
|
|
517
|
+
|
|
518
|
+
Examples
|
|
519
|
+
--------
|
|
520
|
+
|
|
521
|
+
.. manim:: ChangeDefaultAnimation
|
|
522
|
+
|
|
523
|
+
class ChangeDefaultAnimation(Scene):
|
|
524
|
+
def construct(self):
|
|
525
|
+
Rotate.set_default(run_time=2, rate_func=rate_functions.linear)
|
|
526
|
+
Indicate.set_default(color=None)
|
|
527
|
+
|
|
528
|
+
S = Square(color=BLUE, fill_color=BLUE, fill_opacity=0.25)
|
|
529
|
+
self.add(S)
|
|
530
|
+
self.play(Rotate(S, PI))
|
|
531
|
+
self.play(Indicate(S))
|
|
532
|
+
|
|
533
|
+
Rotate.set_default()
|
|
534
|
+
Indicate.set_default()
|
|
535
|
+
|
|
536
|
+
"""
|
|
537
|
+
if kwargs:
|
|
538
|
+
cls.__init__ = partialmethod(cls.__init__, **kwargs)
|
|
539
|
+
else:
|
|
540
|
+
cls.__init__ = cls._original__init__
|
|
541
|
+
|
|
486
542
|
|
|
487
543
|
def prepare_animation(
|
|
488
544
|
anim: Animation | mobject._AnimationBuilder,
|
|
@@ -582,6 +638,90 @@ class Wait(Animation):
|
|
|
582
638
|
pass
|
|
583
639
|
|
|
584
640
|
|
|
641
|
+
class Add(Animation):
|
|
642
|
+
"""Add Mobjects to a scene, without animating them in any other way. This
|
|
643
|
+
is similar to the :meth:`.Scene.add` method, but :class:`Add` is an
|
|
644
|
+
animation which can be grouped into other animations.
|
|
645
|
+
|
|
646
|
+
Parameters
|
|
647
|
+
----------
|
|
648
|
+
mobjects
|
|
649
|
+
One :class:`~.Mobject` or more to add to a scene.
|
|
650
|
+
run_time
|
|
651
|
+
The duration of the animation after adding the ``mobjects``. Defaults
|
|
652
|
+
to 0, which means this is an instant animation without extra wait time
|
|
653
|
+
after adding them.
|
|
654
|
+
**kwargs
|
|
655
|
+
Additional arguments to pass to the parent :class:`Animation` class.
|
|
656
|
+
|
|
657
|
+
Examples
|
|
658
|
+
--------
|
|
659
|
+
|
|
660
|
+
.. manim:: DefaultAddScene
|
|
661
|
+
|
|
662
|
+
class DefaultAddScene(Scene):
|
|
663
|
+
def construct(self):
|
|
664
|
+
text_1 = Text("I was added with Add!")
|
|
665
|
+
text_2 = Text("Me too!")
|
|
666
|
+
text_3 = Text("And me!")
|
|
667
|
+
texts = VGroup(text_1, text_2, text_3).arrange(DOWN)
|
|
668
|
+
rect = SurroundingRectangle(texts, buff=0.5)
|
|
669
|
+
|
|
670
|
+
self.play(
|
|
671
|
+
Create(rect, run_time=3.0),
|
|
672
|
+
Succession(
|
|
673
|
+
Wait(1.0),
|
|
674
|
+
# You can Add a Mobject in the middle of an animation...
|
|
675
|
+
Add(text_1),
|
|
676
|
+
Wait(1.0),
|
|
677
|
+
# ...or multiple Mobjects at once!
|
|
678
|
+
Add(text_2, text_3),
|
|
679
|
+
),
|
|
680
|
+
)
|
|
681
|
+
self.wait()
|
|
682
|
+
|
|
683
|
+
.. manim:: AddWithRunTimeScene
|
|
684
|
+
|
|
685
|
+
class AddWithRunTimeScene(Scene):
|
|
686
|
+
def construct(self):
|
|
687
|
+
# A 5x5 grid of circles
|
|
688
|
+
circles = VGroup(
|
|
689
|
+
*[Circle(radius=0.5) for _ in range(25)]
|
|
690
|
+
).arrange_in_grid(5, 5)
|
|
691
|
+
|
|
692
|
+
self.play(
|
|
693
|
+
Succession(
|
|
694
|
+
# Add a run_time of 0.2 to wait for 0.2 seconds after
|
|
695
|
+
# adding the circle, instead of using Wait(0.2) after Add!
|
|
696
|
+
*[Add(circle, run_time=0.2) for circle in circles],
|
|
697
|
+
rate_func=smooth,
|
|
698
|
+
)
|
|
699
|
+
)
|
|
700
|
+
self.wait()
|
|
701
|
+
"""
|
|
702
|
+
|
|
703
|
+
def __init__(
|
|
704
|
+
self, *mobjects: Mobject, run_time: float = 0.0, **kwargs: Any
|
|
705
|
+
) -> None:
|
|
706
|
+
mobject = mobjects[0] if len(mobjects) == 1 else Group(*mobjects)
|
|
707
|
+
super().__init__(mobject, run_time=run_time, introducer=True, **kwargs)
|
|
708
|
+
|
|
709
|
+
def begin(self) -> None:
|
|
710
|
+
pass
|
|
711
|
+
|
|
712
|
+
def finish(self) -> None:
|
|
713
|
+
pass
|
|
714
|
+
|
|
715
|
+
def clean_up_from_scene(self, scene: Scene) -> None:
|
|
716
|
+
pass
|
|
717
|
+
|
|
718
|
+
def update_mobjects(self, dt: float) -> None:
|
|
719
|
+
pass
|
|
720
|
+
|
|
721
|
+
def interpolate(self, alpha: float) -> None:
|
|
722
|
+
pass
|
|
723
|
+
|
|
724
|
+
|
|
585
725
|
def override_animation(
|
|
586
726
|
animation_class: type[Animation],
|
|
587
727
|
) -> Callable[[Callable], Callable]:
|
manim/animation/composition.py
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import types
|
|
6
|
-
from
|
|
6
|
+
from collections.abc import Iterable, Sequence
|
|
7
|
+
from typing import TYPE_CHECKING, Callable
|
|
7
8
|
|
|
8
9
|
import numpy as np
|
|
9
10
|
|
|
@@ -81,15 +82,10 @@ class AnimationGroup(Animation):
|
|
|
81
82
|
return list(self.group)
|
|
82
83
|
|
|
83
84
|
def begin(self) -> None:
|
|
84
|
-
if self.
|
|
85
|
-
tmp = (
|
|
86
|
-
"Please set the run_time to be positive"
|
|
87
|
-
if len(self.animations) != 0
|
|
88
|
-
else "Please add at least one Animation with positive run_time"
|
|
89
|
-
)
|
|
85
|
+
if not self.animations:
|
|
90
86
|
raise ValueError(
|
|
91
|
-
f"{self}
|
|
92
|
-
|
|
87
|
+
f"Trying to play {self} without animations, this is not supported. "
|
|
88
|
+
"Please add at least one subanimation."
|
|
93
89
|
)
|
|
94
90
|
self.anim_group_time = 0.0
|
|
95
91
|
if self.suspend_mobject_updating:
|
|
@@ -102,7 +98,8 @@ class AnimationGroup(Animation):
|
|
|
102
98
|
anim._setup_scene(scene)
|
|
103
99
|
|
|
104
100
|
def finish(self) -> None:
|
|
105
|
-
self.
|
|
101
|
+
for anim in self.animations:
|
|
102
|
+
anim.finish()
|
|
106
103
|
self.anims_begun[:] = True
|
|
107
104
|
self.anims_finished[:] = True
|
|
108
105
|
if self.suspend_mobject_updating:
|
|
@@ -178,11 +175,13 @@ class AnimationGroup(Animation):
|
|
|
178
175
|
]
|
|
179
176
|
|
|
180
177
|
run_times = to_update["end"] - to_update["start"]
|
|
178
|
+
with_zero_run_time = run_times == 0
|
|
179
|
+
run_times[with_zero_run_time] = 1
|
|
181
180
|
sub_alphas = (anim_group_time - to_update["start"]) / run_times
|
|
182
181
|
if time_goes_back:
|
|
183
|
-
sub_alphas[sub_alphas < 0] = 0
|
|
182
|
+
sub_alphas[(sub_alphas < 0) | with_zero_run_time] = 0
|
|
184
183
|
else:
|
|
185
|
-
sub_alphas[sub_alphas > 1] = 1
|
|
184
|
+
sub_alphas[(sub_alphas > 1) | with_zero_run_time] = 1
|
|
186
185
|
|
|
187
186
|
for anim_to_update, sub_alpha in zip(to_update["anim"], sub_alphas):
|
|
188
187
|
anim_to_update.interpolate(sub_alpha)
|
|
@@ -233,7 +232,11 @@ class Succession(AnimationGroup):
|
|
|
233
232
|
super().__init__(*animations, lag_ratio=lag_ratio, **kwargs)
|
|
234
233
|
|
|
235
234
|
def begin(self) -> None:
|
|
236
|
-
|
|
235
|
+
if not self.animations:
|
|
236
|
+
raise ValueError(
|
|
237
|
+
f"Trying to play {self} without animations, this is not supported. "
|
|
238
|
+
"Please add at least one subanimation."
|
|
239
|
+
)
|
|
237
240
|
self.update_active_animation(0)
|
|
238
241
|
|
|
239
242
|
def finish(self) -> None:
|