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
|
@@ -1,40 +1,105 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import re
|
|
5
|
+
import sys
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
4
7
|
|
|
5
8
|
from cloup import Choice, option, option_group
|
|
6
9
|
|
|
7
10
|
from manim.constants import QUALITIES, RendererType
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from click import Context, Option
|
|
10
14
|
|
|
11
15
|
__all__ = ["render_options"]
|
|
12
16
|
|
|
17
|
+
logger = logging.getLogger("manim")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def validate_scene_range(
|
|
21
|
+
ctx: Context, param: Option, value: str | None
|
|
22
|
+
) -> tuple[int] | tuple[int, int] | None:
|
|
23
|
+
"""If the ``value`` string is given, extract from it the scene range, which
|
|
24
|
+
should be in any of these formats: 'start', 'start;end', 'start,end' or
|
|
25
|
+
'start-end'. Otherwise, return ``None``.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
ctx
|
|
30
|
+
The Click context.
|
|
31
|
+
param
|
|
32
|
+
A Click option.
|
|
33
|
+
value
|
|
34
|
+
The optional string which will be parsed.
|
|
35
|
+
|
|
36
|
+
Returns
|
|
37
|
+
-------
|
|
38
|
+
tuple[int] | tuple[int, int] | None
|
|
39
|
+
If ``value`` is ``None``, the return value is ``None``. Otherwise, it's
|
|
40
|
+
the scene range, given by a tuple which may contain a single value
|
|
41
|
+
``start`` or two values ``start`` and ``end``.
|
|
42
|
+
|
|
43
|
+
Raises
|
|
44
|
+
------
|
|
45
|
+
ValueError
|
|
46
|
+
If ``value`` has an invalid format.
|
|
47
|
+
"""
|
|
48
|
+
if value is None:
|
|
49
|
+
return None
|
|
13
50
|
|
|
14
|
-
def validate_scene_range(ctx, param, value):
|
|
15
51
|
try:
|
|
16
52
|
start = int(value)
|
|
17
53
|
return (start,)
|
|
18
54
|
except Exception:
|
|
19
55
|
pass
|
|
20
56
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
57
|
+
try:
|
|
58
|
+
start, end = map(int, re.split(r"[;,\-]", value))
|
|
59
|
+
except Exception:
|
|
60
|
+
logger.error("Couldn't determine a range for -n option.")
|
|
61
|
+
sys.exit()
|
|
62
|
+
|
|
63
|
+
return start, end
|
|
28
64
|
|
|
29
65
|
|
|
30
|
-
def validate_resolution(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
66
|
+
def validate_resolution(
|
|
67
|
+
ctx: Context, param: Option, value: str | None
|
|
68
|
+
) -> tuple[int, int] | None:
|
|
69
|
+
"""If the ``value`` string is given, extract from it the resolution, which
|
|
70
|
+
should be in any of these formats: 'W;H', 'W,H' or 'W-H'. Otherwise, return
|
|
71
|
+
``None``.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
ctx
|
|
76
|
+
The Click context.
|
|
77
|
+
param
|
|
78
|
+
A Click option.
|
|
79
|
+
value
|
|
80
|
+
The optional string which will be parsed.
|
|
81
|
+
|
|
82
|
+
Returns
|
|
83
|
+
-------
|
|
84
|
+
tuple[int, int] | None
|
|
85
|
+
If ``value`` is ``None``, the return value is ``None``. Otherwise, it's
|
|
86
|
+
the resolution as a ``(W, H)`` tuple.
|
|
87
|
+
|
|
88
|
+
Raises
|
|
89
|
+
------
|
|
90
|
+
ValueError
|
|
91
|
+
If ``value`` has an invalid format.
|
|
92
|
+
"""
|
|
93
|
+
if value is None:
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
width, height = map(int, re.split(r"[;,\-]", value))
|
|
98
|
+
except Exception:
|
|
99
|
+
logger.error("Resolution option is invalid.")
|
|
100
|
+
sys.exit()
|
|
101
|
+
|
|
102
|
+
return width, height
|
|
38
103
|
|
|
39
104
|
|
|
40
105
|
render_options = option_group(
|
|
@@ -71,14 +136,14 @@ render_options = option_group(
|
|
|
71
136
|
"--quality",
|
|
72
137
|
default=None,
|
|
73
138
|
type=Choice(
|
|
74
|
-
list(reversed([q["flag"] for q in QUALITIES.values() if q["flag"]])),
|
|
139
|
+
list(reversed([q["flag"] for q in QUALITIES.values() if q["flag"]])),
|
|
75
140
|
case_sensitive=False,
|
|
76
141
|
),
|
|
77
142
|
help="Render quality at the follow resolution framerates, respectively: "
|
|
78
143
|
+ ", ".join(
|
|
79
144
|
reversed(
|
|
80
145
|
[
|
|
81
|
-
f
|
|
146
|
+
f"{q['pixel_width']}x{q['pixel_height']} {q['frame_rate']}FPS"
|
|
82
147
|
for q in QUALITIES.values()
|
|
83
148
|
if q["flag"]
|
|
84
149
|
]
|
manim/constants.py
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Constant definitions.
|
|
3
|
-
"""
|
|
1
|
+
"""Constant definitions."""
|
|
4
2
|
|
|
5
3
|
from __future__ import annotations
|
|
6
4
|
|
|
7
5
|
from enum import Enum
|
|
6
|
+
from typing import TypedDict
|
|
8
7
|
|
|
9
8
|
import numpy as np
|
|
10
9
|
from cloup import Context
|
|
@@ -199,8 +198,16 @@ TAU = 2 * PI
|
|
|
199
198
|
DEGREES = TAU / 360
|
|
200
199
|
"""The exchange rate between radians and degrees."""
|
|
201
200
|
|
|
201
|
+
|
|
202
|
+
class QualityDict(TypedDict):
|
|
203
|
+
flag: str | None
|
|
204
|
+
pixel_height: int
|
|
205
|
+
pixel_width: int
|
|
206
|
+
frame_rate: int
|
|
207
|
+
|
|
208
|
+
|
|
202
209
|
# Video qualities
|
|
203
|
-
QUALITIES: dict[str,
|
|
210
|
+
QUALITIES: dict[str, QualityDict] = {
|
|
204
211
|
"fourk_quality": {
|
|
205
212
|
"flag": "k",
|
|
206
213
|
"pixel_height": 2160,
|
manim/mobject/frame.py
CHANGED
manim/mobject/geometry/arc.py
CHANGED
|
@@ -44,7 +44,7 @@ __all__ = [
|
|
|
44
44
|
|
|
45
45
|
import itertools
|
|
46
46
|
import warnings
|
|
47
|
-
from typing import TYPE_CHECKING
|
|
47
|
+
from typing import TYPE_CHECKING, cast
|
|
48
48
|
|
|
49
49
|
import numpy as np
|
|
50
50
|
from typing_extensions import Self
|
|
@@ -63,11 +63,19 @@ from manim.utils.space_ops import (
|
|
|
63
63
|
)
|
|
64
64
|
|
|
65
65
|
if TYPE_CHECKING:
|
|
66
|
+
from collections.abc import Iterable
|
|
67
|
+
from typing import Any
|
|
68
|
+
|
|
66
69
|
import manim.mobject.geometry.tips as tips
|
|
67
70
|
from manim.mobject.mobject import Mobject
|
|
68
71
|
from manim.mobject.text.tex_mobject import SingleStringMathTex, Tex
|
|
69
72
|
from manim.mobject.text.text_mobject import Text
|
|
70
|
-
from manim.typing import
|
|
73
|
+
from manim.typing import (
|
|
74
|
+
Point3D,
|
|
75
|
+
Point3DLike,
|
|
76
|
+
QuadraticSpline,
|
|
77
|
+
Vector3D,
|
|
78
|
+
)
|
|
71
79
|
|
|
72
80
|
|
|
73
81
|
class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
@@ -93,7 +101,7 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
|
93
101
|
tip_length: float = DEFAULT_ARROW_TIP_LENGTH,
|
|
94
102
|
normal_vector: Vector3D = OUT,
|
|
95
103
|
tip_style: dict = {},
|
|
96
|
-
**kwargs,
|
|
104
|
+
**kwargs: Any,
|
|
97
105
|
) -> None:
|
|
98
106
|
self.tip_length: float = tip_length
|
|
99
107
|
self.normal_vector: Vector3D = normal_vector
|
|
@@ -126,10 +134,10 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
|
126
134
|
def create_tip(
|
|
127
135
|
self,
|
|
128
136
|
tip_shape: type[tips.ArrowTip] | None = None,
|
|
129
|
-
tip_length: float = None,
|
|
130
|
-
tip_width: float = None,
|
|
137
|
+
tip_length: float | None = None,
|
|
138
|
+
tip_width: float | None = None,
|
|
131
139
|
at_start: bool = False,
|
|
132
|
-
):
|
|
140
|
+
) -> tips.ArrowTip:
|
|
133
141
|
"""Stylises the tip, positions it spatially, and returns
|
|
134
142
|
the newly instantiated tip to the caller.
|
|
135
143
|
"""
|
|
@@ -142,13 +150,13 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
|
142
150
|
tip_shape: type[tips.ArrowTip] | None = None,
|
|
143
151
|
tip_length: float | None = None,
|
|
144
152
|
tip_width: float | None = None,
|
|
145
|
-
):
|
|
153
|
+
) -> tips.ArrowTip | tips.ArrowTriangleFilledTip:
|
|
146
154
|
"""Returns a tip that has been stylistically configured,
|
|
147
155
|
but has not yet been given a position in space.
|
|
148
156
|
"""
|
|
149
157
|
from manim.mobject.geometry.tips import ArrowTriangleFilledTip
|
|
150
158
|
|
|
151
|
-
style = {}
|
|
159
|
+
style: dict[str, Any] = {}
|
|
152
160
|
|
|
153
161
|
if tip_shape is None:
|
|
154
162
|
tip_shape = ArrowTriangleFilledTip
|
|
@@ -166,7 +174,7 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
|
166
174
|
tip = tip_shape(length=tip_length, **style)
|
|
167
175
|
return tip
|
|
168
176
|
|
|
169
|
-
def position_tip(self, tip: tips.ArrowTip, at_start: bool = False):
|
|
177
|
+
def position_tip(self, tip: tips.ArrowTip, at_start: bool = False) -> tips.ArrowTip:
|
|
170
178
|
# Last two control points, defining both
|
|
171
179
|
# the end, and the tangency direction
|
|
172
180
|
if at_start:
|
|
@@ -175,16 +183,18 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
|
175
183
|
else:
|
|
176
184
|
handle = self.get_last_handle()
|
|
177
185
|
anchor = self.get_end()
|
|
178
|
-
angles = cartesian_to_spherical(handle - anchor)
|
|
186
|
+
angles = cartesian_to_spherical((handle - anchor).tolist())
|
|
179
187
|
tip.rotate(
|
|
180
188
|
angles[1] - PI - tip.tip_angle,
|
|
181
189
|
) # Rotates the tip along the azimuthal
|
|
182
190
|
if not hasattr(self, "_init_positioning_axis"):
|
|
183
|
-
axis =
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
191
|
+
axis = np.array(
|
|
192
|
+
[
|
|
193
|
+
np.sin(angles[1]),
|
|
194
|
+
-np.cos(angles[1]),
|
|
195
|
+
0,
|
|
196
|
+
]
|
|
197
|
+
) # Obtains the perpendicular of the tip
|
|
188
198
|
tip.rotate(
|
|
189
199
|
-angles[2] + PI / 2,
|
|
190
200
|
axis=axis,
|
|
@@ -244,23 +254,33 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
|
244
254
|
result.add(self.start_tip)
|
|
245
255
|
return result
|
|
246
256
|
|
|
247
|
-
def get_tip(self):
|
|
257
|
+
def get_tip(self) -> VMobject:
|
|
248
258
|
"""Returns the TipableVMobject instance's (first) tip,
|
|
249
|
-
otherwise throws an exception.
|
|
259
|
+
otherwise throws an exception.
|
|
260
|
+
"""
|
|
250
261
|
tips = self.get_tips()
|
|
251
262
|
if len(tips) == 0:
|
|
252
263
|
raise Exception("tip not found")
|
|
253
264
|
else:
|
|
254
|
-
|
|
265
|
+
tip: VMobject = tips[0]
|
|
266
|
+
return tip
|
|
255
267
|
|
|
256
268
|
def get_default_tip_length(self) -> float:
|
|
257
269
|
return self.tip_length
|
|
258
270
|
|
|
259
271
|
def get_first_handle(self) -> Point3D:
|
|
260
|
-
|
|
272
|
+
# Type inference of extracting an element from a list, is not
|
|
273
|
+
# supported by numpy, see this numpy issue
|
|
274
|
+
# https://github.com/numpy/numpy/issues/16544
|
|
275
|
+
first_handle: Point3D = self.points[1]
|
|
276
|
+
return first_handle
|
|
261
277
|
|
|
262
278
|
def get_last_handle(self) -> Point3D:
|
|
263
|
-
|
|
279
|
+
# Type inference of extracting an element from a list, is not
|
|
280
|
+
# supported by numpy, see this numpy issue
|
|
281
|
+
# https://github.com/numpy/numpy/issues/16544
|
|
282
|
+
last_handle: Point3D = self.points[-2]
|
|
283
|
+
return last_handle
|
|
264
284
|
|
|
265
285
|
def get_end(self) -> Point3D:
|
|
266
286
|
if self.has_tip():
|
|
@@ -274,9 +294,9 @@ class TipableVMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
|
274
294
|
else:
|
|
275
295
|
return super().get_start()
|
|
276
296
|
|
|
277
|
-
def get_length(self) ->
|
|
297
|
+
def get_length(self) -> float:
|
|
278
298
|
start, end = self.get_start_and_end()
|
|
279
|
-
return np.linalg.norm(start - end)
|
|
299
|
+
return float(np.linalg.norm(start - end))
|
|
280
300
|
|
|
281
301
|
|
|
282
302
|
class Arc(TipableVMobject):
|
|
@@ -296,20 +316,20 @@ class Arc(TipableVMobject):
|
|
|
296
316
|
|
|
297
317
|
def __init__(
|
|
298
318
|
self,
|
|
299
|
-
radius: float = 1.0,
|
|
319
|
+
radius: float | None = 1.0,
|
|
300
320
|
start_angle: float = 0,
|
|
301
321
|
angle: float = TAU / 4,
|
|
302
322
|
num_components: int = 9,
|
|
303
|
-
arc_center:
|
|
304
|
-
**kwargs,
|
|
323
|
+
arc_center: Point3DLike = ORIGIN,
|
|
324
|
+
**kwargs: Any,
|
|
305
325
|
):
|
|
306
326
|
if radius is None: # apparently None is passed by ArcBetweenPoints
|
|
307
327
|
radius = 1.0
|
|
308
328
|
self.radius = radius
|
|
309
|
-
self.num_components
|
|
310
|
-
self.arc_center: Point3D = arc_center
|
|
311
|
-
self.start_angle
|
|
312
|
-
self.angle
|
|
329
|
+
self.num_components = num_components
|
|
330
|
+
self.arc_center: Point3D = np.asarray(arc_center)
|
|
331
|
+
self.start_angle = start_angle
|
|
332
|
+
self.angle = angle
|
|
313
333
|
self._failed_to_get_center: bool = False
|
|
314
334
|
super().__init__(**kwargs)
|
|
315
335
|
|
|
@@ -335,7 +355,7 @@ class Arc(TipableVMobject):
|
|
|
335
355
|
@staticmethod
|
|
336
356
|
def _create_quadratic_bezier_points(
|
|
337
357
|
angle: float, start_angle: float = 0, n_components: int = 8
|
|
338
|
-
) ->
|
|
358
|
+
) -> QuadraticSpline:
|
|
339
359
|
samples = np.array(
|
|
340
360
|
[
|
|
341
361
|
[np.cos(a), np.sin(a), 0]
|
|
@@ -374,8 +394,9 @@ class Arc(TipableVMobject):
|
|
|
374
394
|
tangent_vectors[:, 1] = anchors[:, 0]
|
|
375
395
|
tangent_vectors[:, 0] = -anchors[:, 1]
|
|
376
396
|
# Use tangent vectors to deduce anchors
|
|
377
|
-
|
|
378
|
-
|
|
397
|
+
factor = 4 / 3 * np.tan(d_theta / 4)
|
|
398
|
+
handles1 = anchors[:-1] + factor * tangent_vectors[:-1]
|
|
399
|
+
handles2 = anchors[1:] - factor * tangent_vectors[1:]
|
|
379
400
|
self.set_anchors_and_handles(anchors[:-1], handles1, handles2, anchors[1:])
|
|
380
401
|
|
|
381
402
|
def get_arc_center(self, warning: bool = True) -> Point3D:
|
|
@@ -400,16 +421,21 @@ class Arc(TipableVMobject):
|
|
|
400
421
|
return line_intersection(line1=(a1, a1 + n1), line2=(a2, a2 + n2))
|
|
401
422
|
except Exception:
|
|
402
423
|
if warning:
|
|
403
|
-
warnings.warn(
|
|
424
|
+
warnings.warn(
|
|
425
|
+
"Can't find Arc center, using ORIGIN instead", stacklevel=1
|
|
426
|
+
)
|
|
404
427
|
self._failed_to_get_center = True
|
|
405
428
|
return np.array(ORIGIN)
|
|
406
429
|
|
|
407
|
-
def move_arc_center_to(self, point:
|
|
430
|
+
def move_arc_center_to(self, point: Point3DLike) -> Self:
|
|
408
431
|
self.shift(point - self.get_arc_center())
|
|
409
432
|
return self
|
|
410
433
|
|
|
411
434
|
def stop_angle(self) -> float:
|
|
412
|
-
return
|
|
435
|
+
return cast(
|
|
436
|
+
float,
|
|
437
|
+
angle_of_vector(self.points[-1] - self.get_arc_center()) % TAU,
|
|
438
|
+
)
|
|
413
439
|
|
|
414
440
|
|
|
415
441
|
class ArcBetweenPoints(Arc):
|
|
@@ -433,11 +459,11 @@ class ArcBetweenPoints(Arc):
|
|
|
433
459
|
|
|
434
460
|
def __init__(
|
|
435
461
|
self,
|
|
436
|
-
start:
|
|
437
|
-
end:
|
|
462
|
+
start: Point3DLike,
|
|
463
|
+
end: Point3DLike,
|
|
438
464
|
angle: float = TAU / 4,
|
|
439
|
-
radius: float = None,
|
|
440
|
-
**kwargs,
|
|
465
|
+
radius: float | None = None,
|
|
466
|
+
**kwargs: Any,
|
|
441
467
|
) -> None:
|
|
442
468
|
if radius is not None:
|
|
443
469
|
self.radius = radius
|
|
@@ -457,19 +483,24 @@ class ArcBetweenPoints(Arc):
|
|
|
457
483
|
|
|
458
484
|
super().__init__(radius=radius, angle=angle, **kwargs)
|
|
459
485
|
if angle == 0:
|
|
460
|
-
self.set_points_as_corners([LEFT, RIGHT])
|
|
486
|
+
self.set_points_as_corners(np.array([LEFT, RIGHT]))
|
|
461
487
|
self.put_start_and_end_on(start, end)
|
|
462
488
|
|
|
463
489
|
if radius is None:
|
|
464
490
|
center = self.get_arc_center(warning=False)
|
|
465
491
|
if not self._failed_to_get_center:
|
|
466
|
-
|
|
492
|
+
# np.linalg.norm returns floating[Any] which is not compatible with float
|
|
493
|
+
self.radius = cast(
|
|
494
|
+
float, np.linalg.norm(np.array(start) - np.array(center))
|
|
495
|
+
)
|
|
467
496
|
else:
|
|
468
497
|
self.radius = np.inf
|
|
469
498
|
|
|
470
499
|
|
|
471
500
|
class CurvedArrow(ArcBetweenPoints):
|
|
472
|
-
def __init__(
|
|
501
|
+
def __init__(
|
|
502
|
+
self, start_point: Point3DLike, end_point: Point3DLike, **kwargs: Any
|
|
503
|
+
) -> None:
|
|
473
504
|
from manim.mobject.geometry.tips import ArrowTriangleFilledTip
|
|
474
505
|
|
|
475
506
|
tip_shape = kwargs.pop("tip_shape", ArrowTriangleFilledTip)
|
|
@@ -478,7 +509,9 @@ class CurvedArrow(ArcBetweenPoints):
|
|
|
478
509
|
|
|
479
510
|
|
|
480
511
|
class CurvedDoubleArrow(CurvedArrow):
|
|
481
|
-
def __init__(
|
|
512
|
+
def __init__(
|
|
513
|
+
self, start_point: Point3DLike, end_point: Point3DLike, **kwargs: Any
|
|
514
|
+
) -> None:
|
|
482
515
|
if "tip_shape_end" in kwargs:
|
|
483
516
|
kwargs["tip_shape"] = kwargs.pop("tip_shape_end")
|
|
484
517
|
from manim.mobject.geometry.tips import ArrowTriangleFilledTip
|
|
@@ -517,7 +550,7 @@ class Circle(Arc):
|
|
|
517
550
|
self,
|
|
518
551
|
radius: float | None = None,
|
|
519
552
|
color: ParsableManimColor = RED,
|
|
520
|
-
**kwargs,
|
|
553
|
+
**kwargs: Any,
|
|
521
554
|
) -> None:
|
|
522
555
|
super().__init__(
|
|
523
556
|
radius=radius,
|
|
@@ -569,7 +602,6 @@ class Circle(Arc):
|
|
|
569
602
|
group = Group(group1, group2, group3).arrange(buff=1)
|
|
570
603
|
self.add(group)
|
|
571
604
|
"""
|
|
572
|
-
|
|
573
605
|
# Ignores dim_to_match and stretch; result will always be a circle
|
|
574
606
|
# TODO: Perhaps create an ellipse class to handle single-dimension stretching
|
|
575
607
|
|
|
@@ -609,14 +641,15 @@ class Circle(Arc):
|
|
|
609
641
|
self.add(circle, s1, s2)
|
|
610
642
|
|
|
611
643
|
"""
|
|
612
|
-
|
|
613
644
|
start_angle = angle_of_vector(self.points[0] - self.get_center())
|
|
614
645
|
proportion = (angle - start_angle) / TAU
|
|
615
646
|
proportion -= np.floor(proportion)
|
|
616
647
|
return self.point_from_proportion(proportion)
|
|
617
648
|
|
|
618
649
|
@staticmethod
|
|
619
|
-
def from_three_points(
|
|
650
|
+
def from_three_points(
|
|
651
|
+
p1: Point3DLike, p2: Point3DLike, p3: Point3DLike, **kwargs: Any
|
|
652
|
+
) -> Circle:
|
|
620
653
|
"""Returns a circle passing through the specified
|
|
621
654
|
three points.
|
|
622
655
|
|
|
@@ -636,10 +669,11 @@ class Circle(Arc):
|
|
|
636
669
|
self.add(NumberPlane(), circle, dots)
|
|
637
670
|
"""
|
|
638
671
|
center = line_intersection(
|
|
639
|
-
perpendicular_bisector([p1, p2]),
|
|
640
|
-
perpendicular_bisector([p2, p3]),
|
|
672
|
+
perpendicular_bisector([np.asarray(p1), np.asarray(p2)]),
|
|
673
|
+
perpendicular_bisector([np.asarray(p2), np.asarray(p3)]),
|
|
641
674
|
)
|
|
642
|
-
|
|
675
|
+
# np.linalg.norm returns floating[Any] which is not compatible with float
|
|
676
|
+
radius = cast(float, np.linalg.norm(p1 - center))
|
|
643
677
|
return Circle(radius=radius, **kwargs).shift(center)
|
|
644
678
|
|
|
645
679
|
|
|
@@ -676,12 +710,12 @@ class Dot(Circle):
|
|
|
676
710
|
|
|
677
711
|
def __init__(
|
|
678
712
|
self,
|
|
679
|
-
point:
|
|
713
|
+
point: Point3DLike = ORIGIN,
|
|
680
714
|
radius: float = DEFAULT_DOT_RADIUS,
|
|
681
715
|
stroke_width: float = 0,
|
|
682
716
|
fill_opacity: float = 1.0,
|
|
683
717
|
color: ParsableManimColor = WHITE,
|
|
684
|
-
**kwargs,
|
|
718
|
+
**kwargs: Any,
|
|
685
719
|
) -> None:
|
|
686
720
|
super().__init__(
|
|
687
721
|
arc_center=point,
|
|
@@ -702,7 +736,7 @@ class AnnotationDot(Dot):
|
|
|
702
736
|
stroke_width: float = 5,
|
|
703
737
|
stroke_color: ParsableManimColor = WHITE,
|
|
704
738
|
fill_color: ParsableManimColor = BLUE,
|
|
705
|
-
**kwargs,
|
|
739
|
+
**kwargs: Any,
|
|
706
740
|
) -> None:
|
|
707
741
|
super().__init__(
|
|
708
742
|
radius=radius,
|
|
@@ -751,12 +785,12 @@ class LabeledDot(Dot):
|
|
|
751
785
|
self,
|
|
752
786
|
label: str | SingleStringMathTex | Text | Tex,
|
|
753
787
|
radius: float | None = None,
|
|
754
|
-
**kwargs,
|
|
788
|
+
**kwargs: Any,
|
|
755
789
|
) -> None:
|
|
756
790
|
if isinstance(label, str):
|
|
757
791
|
from manim import MathTex
|
|
758
792
|
|
|
759
|
-
rendered_label = MathTex(label, color=BLACK)
|
|
793
|
+
rendered_label: VMobject = MathTex(label, color=BLACK)
|
|
760
794
|
else:
|
|
761
795
|
rendered_label = label
|
|
762
796
|
|
|
@@ -792,7 +826,7 @@ class Ellipse(Circle):
|
|
|
792
826
|
self.add(ellipse_group)
|
|
793
827
|
"""
|
|
794
828
|
|
|
795
|
-
def __init__(self, width: float = 2, height: float = 1, **kwargs) -> None:
|
|
829
|
+
def __init__(self, width: float = 2, height: float = 1, **kwargs: Any) -> None:
|
|
796
830
|
super().__init__(**kwargs)
|
|
797
831
|
self.stretch_to_fit_width(width)
|
|
798
832
|
self.stretch_to_fit_height(height)
|
|
@@ -853,7 +887,7 @@ class AnnularSector(Arc):
|
|
|
853
887
|
fill_opacity: float = 1,
|
|
854
888
|
stroke_width: float = 0,
|
|
855
889
|
color: ParsableManimColor = WHITE,
|
|
856
|
-
**kwargs,
|
|
890
|
+
**kwargs: Any,
|
|
857
891
|
) -> None:
|
|
858
892
|
self.inner_radius = inner_radius
|
|
859
893
|
self.outer_radius = outer_radius
|
|
@@ -895,17 +929,15 @@ class Sector(AnnularSector):
|
|
|
895
929
|
|
|
896
930
|
class ExampleSector(Scene):
|
|
897
931
|
def construct(self):
|
|
898
|
-
sector = Sector(
|
|
899
|
-
sector2 = Sector(
|
|
932
|
+
sector = Sector(radius=2)
|
|
933
|
+
sector2 = Sector(radius=2.5, angle=60*DEGREES).move_to([-3, 0, 0])
|
|
900
934
|
sector.set_color(RED)
|
|
901
935
|
sector2.set_color(PINK)
|
|
902
936
|
self.add(sector, sector2)
|
|
903
937
|
"""
|
|
904
938
|
|
|
905
|
-
def __init__(
|
|
906
|
-
|
|
907
|
-
) -> None:
|
|
908
|
-
super().__init__(inner_radius=inner_radius, outer_radius=outer_radius, **kwargs)
|
|
939
|
+
def __init__(self, radius: float = 1, **kwargs: Any) -> None:
|
|
940
|
+
super().__init__(inner_radius=0, outer_radius=radius, **kwargs)
|
|
909
941
|
|
|
910
942
|
|
|
911
943
|
class Annulus(Circle):
|
|
@@ -934,13 +966,13 @@ class Annulus(Circle):
|
|
|
934
966
|
|
|
935
967
|
def __init__(
|
|
936
968
|
self,
|
|
937
|
-
inner_radius: float
|
|
938
|
-
outer_radius: float
|
|
969
|
+
inner_radius: float = 1,
|
|
970
|
+
outer_radius: float = 2,
|
|
939
971
|
fill_opacity: float = 1,
|
|
940
972
|
stroke_width: float = 0,
|
|
941
973
|
color: ParsableManimColor = WHITE,
|
|
942
974
|
mark_paths_closed: bool = False,
|
|
943
|
-
**kwargs,
|
|
975
|
+
**kwargs: Any,
|
|
944
976
|
) -> None:
|
|
945
977
|
self.mark_paths_closed = mark_paths_closed # is this even used?
|
|
946
978
|
self.inner_radius = inner_radius
|
|
@@ -986,11 +1018,11 @@ class CubicBezier(VMobject, metaclass=ConvertToOpenGL):
|
|
|
986
1018
|
|
|
987
1019
|
def __init__(
|
|
988
1020
|
self,
|
|
989
|
-
start_anchor:
|
|
990
|
-
start_handle:
|
|
991
|
-
end_handle:
|
|
992
|
-
end_anchor:
|
|
993
|
-
**kwargs,
|
|
1021
|
+
start_anchor: Point3DLike,
|
|
1022
|
+
start_handle: Point3DLike,
|
|
1023
|
+
end_handle: Point3DLike,
|
|
1024
|
+
end_anchor: Point3DLike,
|
|
1025
|
+
**kwargs: Any,
|
|
994
1026
|
) -> None:
|
|
995
1027
|
super().__init__(**kwargs)
|
|
996
1028
|
self.add_cubic_bezier_curve(start_anchor, start_handle, end_handle, end_anchor)
|
|
@@ -1077,18 +1109,20 @@ class ArcPolygon(VMobject, metaclass=ConvertToOpenGL):
|
|
|
1077
1109
|
|
|
1078
1110
|
def __init__(
|
|
1079
1111
|
self,
|
|
1080
|
-
*vertices:
|
|
1112
|
+
*vertices: Point3DLike,
|
|
1081
1113
|
angle: float = PI / 4,
|
|
1082
1114
|
radius: float | None = None,
|
|
1083
1115
|
arc_config: list[dict] | None = None,
|
|
1084
|
-
**kwargs,
|
|
1116
|
+
**kwargs: Any,
|
|
1085
1117
|
) -> None:
|
|
1086
1118
|
n = len(vertices)
|
|
1087
1119
|
point_pairs = [(vertices[k], vertices[(k + 1) % n]) for k in range(n)]
|
|
1088
1120
|
|
|
1089
1121
|
if not arc_config:
|
|
1090
1122
|
if radius:
|
|
1091
|
-
all_arc_configs = itertools.repeat(
|
|
1123
|
+
all_arc_configs: Iterable[dict] = itertools.repeat(
|
|
1124
|
+
{"radius": radius}, len(point_pairs)
|
|
1125
|
+
)
|
|
1092
1126
|
else:
|
|
1093
1127
|
all_arc_configs = itertools.repeat({"angle": angle}, len(point_pairs))
|
|
1094
1128
|
elif isinstance(arc_config, dict):
|
|
@@ -1220,7 +1254,7 @@ class ArcPolygonFromArcs(VMobject, metaclass=ConvertToOpenGL):
|
|
|
1220
1254
|
self.wait(2)
|
|
1221
1255
|
"""
|
|
1222
1256
|
|
|
1223
|
-
def __init__(self, *arcs: Arc | ArcBetweenPoints, **kwargs) -> None:
|
|
1257
|
+
def __init__(self, *arcs: Arc | ArcBetweenPoints, **kwargs: Any) -> None:
|
|
1224
1258
|
if not all(isinstance(m, (Arc, ArcBetweenPoints)) for m in arcs):
|
|
1225
1259
|
raise ValueError(
|
|
1226
1260
|
"All ArcPolygon submobjects must be of type Arc/ArcBetweenPoints",
|