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.
- manim/__init__.py +11 -6
- manim/__main__.py +62 -19
- manim/_config/__init__.py +10 -9
- manim/_config/cli_colors.py +26 -9
- manim/_config/default.cfg +1 -3
- manim/_config/logger_utils.py +23 -13
- manim/_config/utils.py +662 -468
- manim/animation/animation.py +164 -18
- manim/animation/changing.py +34 -23
- manim/animation/composition.py +265 -67
- manim/animation/creation.py +208 -26
- manim/animation/fading.py +16 -18
- manim/animation/growing.py +35 -15
- manim/animation/indication.py +150 -76
- manim/animation/movement.py +56 -22
- manim/animation/numbers.py +64 -6
- manim/animation/rotation.py +78 -7
- manim/animation/specialized.py +6 -7
- manim/animation/speedmodifier.py +13 -10
- manim/animation/transform.py +14 -11
- manim/animation/transform_matching_parts.py +3 -4
- manim/animation/updaters/mobject_update_utils.py +152 -30
- manim/animation/updaters/update.py +10 -7
- manim/camera/camera.py +182 -118
- manim/camera/mapping_camera.py +34 -3
- manim/camera/moving_camera.py +95 -74
- manim/camera/multi_camera.py +23 -15
- manim/camera/three_d_camera.py +70 -52
- manim/cli/__init__.py +17 -0
- manim/cli/cfg/group.py +76 -44
- manim/cli/checkhealth/checks.py +192 -0
- manim/cli/checkhealth/commands.py +90 -0
- manim/cli/default_group.py +158 -25
- manim/cli/init/commands.py +33 -25
- manim/cli/plugins/commands.py +16 -3
- manim/cli/render/commands.py +72 -60
- manim/cli/render/ease_of_access_options.py +4 -3
- manim/cli/render/global_options.py +59 -17
- manim/cli/render/output_options.py +6 -5
- manim/cli/render/render_options.py +98 -33
- manim/constants.py +109 -59
- manim/data_structures.py +31 -0
- manim/mobject/frame.py +8 -5
- manim/mobject/geometry/__init__.py +1 -0
- manim/mobject/geometry/arc.py +277 -135
- manim/mobject/geometry/boolean_ops.py +32 -31
- manim/mobject/geometry/labeled.py +376 -0
- manim/mobject/geometry/line.py +192 -87
- manim/mobject/geometry/polygram.py +224 -58
- manim/mobject/geometry/shape_matchers.py +61 -25
- manim/mobject/geometry/tips.py +122 -48
- manim/mobject/graph.py +1027 -419
- manim/mobject/graphing/coordinate_systems.py +533 -278
- manim/mobject/graphing/functions.py +53 -32
- manim/mobject/graphing/number_line.py +123 -65
- manim/mobject/graphing/probability.py +88 -62
- manim/mobject/graphing/scale.py +33 -19
- manim/mobject/logo.py +118 -28
- manim/mobject/matrix.py +87 -83
- manim/mobject/mobject.py +912 -442
- manim/mobject/opengl/dot_cloud.py +16 -5
- manim/mobject/opengl/opengl_compatibility.py +4 -2
- manim/mobject/opengl/opengl_geometry.py +254 -153
- manim/mobject/opengl/opengl_image_mobject.py +3 -1
- manim/mobject/opengl/opengl_mobject.py +779 -482
- manim/mobject/opengl/opengl_point_cloud_mobject.py +41 -14
- manim/mobject/opengl/opengl_surface.py +14 -92
- manim/mobject/opengl/opengl_three_dimensions.py +12 -8
- manim/mobject/opengl/opengl_vectorized_mobject.py +98 -100
- manim/mobject/svg/brace.py +173 -41
- manim/mobject/svg/svg_mobject.py +139 -53
- manim/mobject/table.py +61 -68
- manim/mobject/text/code_mobject.py +193 -539
- manim/mobject/text/numbers.py +81 -34
- manim/mobject/text/tex_mobject.py +130 -78
- manim/mobject/text/text_mobject.py +288 -164
- manim/mobject/three_d/polyhedra.py +111 -13
- manim/mobject/three_d/three_d_utils.py +17 -8
- manim/mobject/three_d/three_dimensions.py +239 -106
- manim/mobject/types/image_mobject.py +50 -30
- manim/mobject/types/point_cloud_mobject.py +120 -75
- manim/mobject/types/vectorized_mobject.py +841 -408
- manim/mobject/value_tracker.py +105 -38
- manim/mobject/vector_field.py +50 -31
- manim/opengl/__init__.py +3 -3
- manim/plugins/__init__.py +14 -1
- manim/plugins/plugins_flags.py +10 -14
- manim/renderer/cairo_renderer.py +65 -50
- manim/renderer/opengl_renderer.py +89 -69
- manim/renderer/opengl_renderer_window.py +39 -18
- manim/renderer/shader.py +123 -87
- manim/renderer/shader_wrapper.py +44 -28
- manim/renderer/vectorized_mobject_rendering.py +38 -10
- manim/scene/moving_camera_scene.py +32 -3
- manim/scene/scene.py +507 -242
- manim/scene/scene_file_writer.py +371 -220
- manim/scene/section.py +20 -16
- manim/scene/three_d_scene.py +14 -22
- manim/scene/vector_space_scene.py +223 -129
- manim/scene/zoomed_scene.py +46 -41
- manim/typing.py +990 -0
- manim/utils/bezier.py +1823 -371
- manim/utils/caching.py +12 -5
- manim/utils/color/AS2700.py +236 -0
- manim/utils/color/BS381.py +318 -0
- manim/utils/color/DVIPSNAMES.py +96 -0
- manim/utils/color/SVGNAMES.py +179 -0
- manim/utils/color/X11.py +533 -0
- manim/utils/color/XKCD.py +952 -0
- manim/utils/color/__init__.py +61 -0
- manim/utils/color/core.py +1667 -0
- manim/utils/color/manim_colors.py +218 -0
- manim/utils/commands.py +48 -20
- manim/utils/config_ops.py +39 -19
- manim/utils/debug.py +8 -7
- manim/utils/deprecation.py +86 -39
- manim/utils/docbuild/__init__.py +17 -0
- manim/utils/docbuild/autoaliasattr_directive.py +236 -0
- manim/utils/docbuild/autocolor_directive.py +99 -0
- manim/utils/docbuild/manim_directive.py +94 -41
- manim/utils/docbuild/module_parsing.py +245 -0
- manim/utils/exceptions.py +6 -0
- manim/utils/family.py +5 -3
- manim/utils/family_ops.py +17 -4
- manim/utils/file_ops.py +27 -17
- manim/utils/hashing.py +55 -45
- manim/utils/images.py +13 -7
- manim/utils/ipython_magic.py +13 -7
- manim/utils/iterables.py +163 -120
- manim/utils/module_ops.py +66 -24
- manim/utils/opengl.py +77 -24
- manim/utils/parameter_parsing.py +32 -0
- manim/utils/paths.py +30 -33
- manim/utils/polylabel.py +235 -0
- manim/utils/qhull.py +218 -0
- manim/utils/rate_functions.py +98 -32
- manim/utils/simple_functions.py +25 -33
- manim/utils/sounds.py +7 -1
- manim/utils/space_ops.py +188 -115
- manim/utils/testing/__init__.py +17 -0
- manim/utils/testing/_frames_testers.py +13 -8
- manim/utils/testing/_show_diff.py +5 -3
- manim/utils/testing/_test_class_makers.py +34 -18
- manim/utils/testing/frames_comparison.py +37 -19
- manim/utils/tex.py +130 -198
- manim/utils/tex_file_writing.py +77 -47
- manim/utils/tex_templates.py +2 -1
- manim/utils/unit.py +6 -5
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/METADATA +64 -65
- manim-0.19.1.dist-info/RECORD +220 -0
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/WHEEL +1 -1
- manim-0.19.1.dist-info/entry_points.txt +3 -0
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE.community +1 -1
- manim/cli/new/group.py +0 -189
- manim/communitycolors.py +0 -9
- manim/gui/__init__.py +0 -0
- manim/gui/gui.py +0 -82
- manim/plugins/import_plugins.py +0 -43
- manim/utils/color.py +0 -552
- manim-0.17.0.dist-info/RECORD +0 -206
- manim-0.17.0.dist-info/entry_points.txt +0 -4
- /manim/cli/{new → checkhealth}/__init__.py +0 -0
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE +0 -0
|
@@ -11,37 +11,61 @@ __all__ = [
|
|
|
11
11
|
"DashedVMobject",
|
|
12
12
|
]
|
|
13
13
|
|
|
14
|
-
|
|
15
14
|
import itertools as it
|
|
16
15
|
import sys
|
|
17
|
-
import
|
|
18
|
-
from typing import
|
|
16
|
+
from collections.abc import Callable, Hashable, Iterable, Mapping, Sequence
|
|
17
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
19
18
|
|
|
20
|
-
import colour
|
|
21
19
|
import numpy as np
|
|
22
20
|
from PIL.Image import Image
|
|
23
21
|
|
|
22
|
+
from manim import config
|
|
23
|
+
from manim.constants import *
|
|
24
|
+
from manim.mobject.mobject import Mobject
|
|
24
25
|
from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL
|
|
26
|
+
from manim.mobject.opengl.opengl_mobject import OpenGLMobject
|
|
25
27
|
from manim.mobject.opengl.opengl_vectorized_mobject import OpenGLVMobject
|
|
26
28
|
from manim.mobject.three_d.three_d_utils import (
|
|
27
29
|
get_3d_vmob_gradient_start_and_end_points,
|
|
28
30
|
)
|
|
29
|
-
|
|
30
|
-
from ... import config
|
|
31
|
-
from ...constants import *
|
|
32
|
-
from ...mobject.mobject import Mobject
|
|
33
|
-
from ...utils.bezier import (
|
|
31
|
+
from manim.utils.bezier import (
|
|
34
32
|
bezier,
|
|
35
|
-
|
|
33
|
+
bezier_remap,
|
|
34
|
+
get_smooth_cubic_bezier_handle_points,
|
|
36
35
|
integer_interpolate,
|
|
37
36
|
interpolate,
|
|
38
37
|
partial_bezier_points,
|
|
39
38
|
proportions_along_bezier_curve_for_point,
|
|
40
39
|
)
|
|
41
|
-
from
|
|
42
|
-
from
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
from manim.utils.color import BLACK, WHITE, ManimColor, ParsableManimColor
|
|
41
|
+
from manim.utils.iterables import (
|
|
42
|
+
make_even,
|
|
43
|
+
resize_array,
|
|
44
|
+
stretch_array_to_length,
|
|
45
|
+
tuplify,
|
|
46
|
+
)
|
|
47
|
+
from manim.utils.space_ops import rotate_vector, shoelace_direction
|
|
48
|
+
|
|
49
|
+
if TYPE_CHECKING:
|
|
50
|
+
import numpy.typing as npt
|
|
51
|
+
from typing_extensions import Self
|
|
52
|
+
|
|
53
|
+
from manim.typing import (
|
|
54
|
+
CubicBezierPath,
|
|
55
|
+
CubicBezierPointsLike,
|
|
56
|
+
CubicSpline,
|
|
57
|
+
FloatRGBA,
|
|
58
|
+
FloatRGBA_Array,
|
|
59
|
+
ManimFloat,
|
|
60
|
+
MappingFunction,
|
|
61
|
+
Point2DLike,
|
|
62
|
+
Point3D,
|
|
63
|
+
Point3D_Array,
|
|
64
|
+
Point3DLike,
|
|
65
|
+
Point3DLike_Array,
|
|
66
|
+
Vector3D,
|
|
67
|
+
Vector3DLike,
|
|
68
|
+
)
|
|
45
69
|
|
|
46
70
|
# TODO
|
|
47
71
|
# - Change cubic curve groups to have 4 points instead of 3
|
|
@@ -81,68 +105,86 @@ class VMobject(Mobject):
|
|
|
81
105
|
|
|
82
106
|
def __init__(
|
|
83
107
|
self,
|
|
84
|
-
fill_color=None,
|
|
85
|
-
fill_opacity=0.0,
|
|
86
|
-
stroke_color=None,
|
|
87
|
-
stroke_opacity=1.0,
|
|
88
|
-
stroke_width=DEFAULT_STROKE_WIDTH,
|
|
89
|
-
background_stroke_color=BLACK,
|
|
90
|
-
background_stroke_opacity=1.0,
|
|
91
|
-
background_stroke_width=0,
|
|
92
|
-
sheen_factor=0.0,
|
|
108
|
+
fill_color: ParsableManimColor | None = None,
|
|
109
|
+
fill_opacity: float = 0.0,
|
|
110
|
+
stroke_color: ParsableManimColor | None = None,
|
|
111
|
+
stroke_opacity: float = 1.0,
|
|
112
|
+
stroke_width: float = DEFAULT_STROKE_WIDTH,
|
|
113
|
+
background_stroke_color: ParsableManimColor | None = BLACK,
|
|
114
|
+
background_stroke_opacity: float = 1.0,
|
|
115
|
+
background_stroke_width: float = 0,
|
|
116
|
+
sheen_factor: float = 0.0,
|
|
93
117
|
joint_type: LineJointType | None = None,
|
|
94
|
-
sheen_direction=UL,
|
|
95
|
-
close_new_points=False,
|
|
96
|
-
pre_function_handle_to_anchor_scale_factor=0.01,
|
|
97
|
-
make_smooth_after_applying_functions=False,
|
|
98
|
-
background_image=None,
|
|
99
|
-
shade_in_3d=False,
|
|
118
|
+
sheen_direction: Vector3DLike = UL,
|
|
119
|
+
close_new_points: bool = False,
|
|
120
|
+
pre_function_handle_to_anchor_scale_factor: float = 0.01,
|
|
121
|
+
make_smooth_after_applying_functions: bool = False,
|
|
122
|
+
background_image: Image | str | None = None,
|
|
123
|
+
shade_in_3d: bool = False,
|
|
100
124
|
# TODO, do we care about accounting for varying zoom levels?
|
|
101
|
-
tolerance_for_point_equality=1e-6,
|
|
102
|
-
n_points_per_cubic_curve=4,
|
|
103
|
-
|
|
125
|
+
tolerance_for_point_equality: float = 1e-6,
|
|
126
|
+
n_points_per_cubic_curve: int = 4,
|
|
127
|
+
cap_style: CapStyleType = CapStyleType.AUTO,
|
|
128
|
+
**kwargs: Any,
|
|
104
129
|
):
|
|
105
130
|
self.fill_opacity = fill_opacity
|
|
106
131
|
self.stroke_opacity = stroke_opacity
|
|
107
132
|
self.stroke_width = stroke_width
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
self.
|
|
133
|
+
if background_stroke_color is not None:
|
|
134
|
+
self.background_stroke_color: ManimColor = ManimColor(
|
|
135
|
+
background_stroke_color
|
|
136
|
+
)
|
|
137
|
+
self.background_stroke_opacity: float = background_stroke_opacity
|
|
138
|
+
self.background_stroke_width: float = background_stroke_width
|
|
139
|
+
self.sheen_factor: float = sheen_factor
|
|
140
|
+
self.joint_type: LineJointType = (
|
|
141
|
+
LineJointType.AUTO if joint_type is None else joint_type
|
|
142
|
+
)
|
|
115
143
|
self.sheen_direction = sheen_direction
|
|
116
|
-
self.close_new_points = close_new_points
|
|
117
|
-
self.pre_function_handle_to_anchor_scale_factor = (
|
|
144
|
+
self.close_new_points: bool = close_new_points
|
|
145
|
+
self.pre_function_handle_to_anchor_scale_factor: float = (
|
|
118
146
|
pre_function_handle_to_anchor_scale_factor
|
|
119
147
|
)
|
|
120
|
-
self.make_smooth_after_applying_functions =
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
self.
|
|
124
|
-
self.
|
|
148
|
+
self.make_smooth_after_applying_functions: bool = (
|
|
149
|
+
make_smooth_after_applying_functions
|
|
150
|
+
)
|
|
151
|
+
self.background_image: Image | str | None = background_image
|
|
152
|
+
self.shade_in_3d: bool = shade_in_3d
|
|
153
|
+
self.tolerance_for_point_equality: float = tolerance_for_point_equality
|
|
154
|
+
self.n_points_per_cubic_curve: int = n_points_per_cubic_curve
|
|
155
|
+
self._bezier_t_values: npt.NDArray[float] = np.linspace(
|
|
156
|
+
0, 1, n_points_per_cubic_curve
|
|
157
|
+
)
|
|
158
|
+
self.cap_style: CapStyleType = cap_style
|
|
125
159
|
super().__init__(**kwargs)
|
|
160
|
+
self.submobjects: list[VMobject]
|
|
126
161
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
162
|
+
# TODO: Find where color overwrites are happening and remove the color doubling
|
|
163
|
+
# if "color" in kwargs:
|
|
164
|
+
# fill_color = kwargs["color"]
|
|
165
|
+
# stroke_color = kwargs["color"]
|
|
166
|
+
if fill_color is not None:
|
|
167
|
+
self.fill_color = ManimColor.parse(fill_color)
|
|
168
|
+
if stroke_color is not None:
|
|
169
|
+
self.stroke_color = ManimColor.parse(stroke_color)
|
|
170
|
+
|
|
171
|
+
def _assert_valid_submobjects(self, submobjects: Iterable[VMobject]) -> Self:
|
|
172
|
+
return self._assert_valid_submobjects_internal(submobjects, VMobject)
|
|
131
173
|
|
|
132
174
|
# OpenGL compatibility
|
|
133
175
|
@property
|
|
134
|
-
def n_points_per_curve(self):
|
|
176
|
+
def n_points_per_curve(self) -> int:
|
|
135
177
|
return self.n_points_per_cubic_curve
|
|
136
178
|
|
|
137
|
-
def get_group_class(self):
|
|
179
|
+
def get_group_class(self) -> type[VGroup]:
|
|
138
180
|
return VGroup
|
|
139
181
|
|
|
140
182
|
@staticmethod
|
|
141
|
-
def get_mobject_type_class():
|
|
183
|
+
def get_mobject_type_class() -> type[VMobject]:
|
|
142
184
|
return VMobject
|
|
143
185
|
|
|
144
186
|
# Colors
|
|
145
|
-
def init_colors(self, propagate_colors=True):
|
|
187
|
+
def init_colors(self, propagate_colors: bool = True) -> Self:
|
|
146
188
|
self.set_fill(
|
|
147
189
|
color=self.fill_color,
|
|
148
190
|
opacity=self.fill_opacity,
|
|
@@ -172,7 +214,11 @@ class VMobject(Mobject):
|
|
|
172
214
|
|
|
173
215
|
return self
|
|
174
216
|
|
|
175
|
-
def generate_rgbas_array(
|
|
217
|
+
def generate_rgbas_array(
|
|
218
|
+
self,
|
|
219
|
+
color: ParsableManimColor | Iterable[ManimColor] | None,
|
|
220
|
+
opacity: float | Iterable[float],
|
|
221
|
+
) -> FloatRGBA:
|
|
176
222
|
"""
|
|
177
223
|
First arg can be either a color, or a tuple/list of colors.
|
|
178
224
|
Likewise, opacity can either be a float, or a tuple of floats.
|
|
@@ -180,10 +226,14 @@ class VMobject(Mobject):
|
|
|
180
226
|
one color was passed in, a second slightly light color
|
|
181
227
|
will automatically be added for the gradient
|
|
182
228
|
"""
|
|
183
|
-
colors = [
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
229
|
+
colors: list[ManimColor] = [
|
|
230
|
+
ManimColor(c) if (c is not None) else BLACK for c in tuplify(color)
|
|
231
|
+
]
|
|
232
|
+
opacities: list[float] = [
|
|
233
|
+
o if (o is not None) else 0.0 for o in tuplify(opacity)
|
|
234
|
+
]
|
|
235
|
+
rgbas: FloatRGBA_Array = np.array(
|
|
236
|
+
[c.to_rgba_with_alpha(o) for c, o in zip(*make_even(colors, opacities))],
|
|
187
237
|
)
|
|
188
238
|
|
|
189
239
|
sheen_factor = self.get_sheen_factor()
|
|
@@ -194,7 +244,12 @@ class VMobject(Mobject):
|
|
|
194
244
|
rgbas = np.append(rgbas, light_rgbas, axis=0)
|
|
195
245
|
return rgbas
|
|
196
246
|
|
|
197
|
-
def update_rgbas_array(
|
|
247
|
+
def update_rgbas_array(
|
|
248
|
+
self,
|
|
249
|
+
array_name: str,
|
|
250
|
+
color: ParsableManimColor | Iterable[ManimColor] | None = None,
|
|
251
|
+
opacity: float | None = None,
|
|
252
|
+
) -> Self:
|
|
198
253
|
rgbas = self.generate_rgbas_array(color, opacity)
|
|
199
254
|
if not hasattr(self, array_name):
|
|
200
255
|
setattr(self, array_name, rgbas)
|
|
@@ -217,10 +272,10 @@ class VMobject(Mobject):
|
|
|
217
272
|
|
|
218
273
|
def set_fill(
|
|
219
274
|
self,
|
|
220
|
-
color:
|
|
275
|
+
color: ParsableManimColor | None = None,
|
|
221
276
|
opacity: float | None = None,
|
|
222
277
|
family: bool = True,
|
|
223
|
-
):
|
|
278
|
+
) -> Self:
|
|
224
279
|
"""Set the fill color and fill opacity of a :class:`VMobject`.
|
|
225
280
|
|
|
226
281
|
Parameters
|
|
@@ -260,18 +315,19 @@ class VMobject(Mobject):
|
|
|
260
315
|
for submobject in self.submobjects:
|
|
261
316
|
submobject.set_fill(color, opacity, family)
|
|
262
317
|
self.update_rgbas_array("fill_rgbas", color, opacity)
|
|
318
|
+
self.fill_rgbas: FloatRGBA_Array
|
|
263
319
|
if opacity is not None:
|
|
264
320
|
self.fill_opacity = opacity
|
|
265
321
|
return self
|
|
266
322
|
|
|
267
323
|
def set_stroke(
|
|
268
324
|
self,
|
|
269
|
-
color=None,
|
|
270
|
-
width=None,
|
|
271
|
-
opacity=None,
|
|
325
|
+
color: ParsableManimColor = None,
|
|
326
|
+
width: float | None = None,
|
|
327
|
+
opacity: float | None = None,
|
|
272
328
|
background=False,
|
|
273
|
-
family=True,
|
|
274
|
-
):
|
|
329
|
+
family: bool = True,
|
|
330
|
+
) -> Self:
|
|
275
331
|
if family:
|
|
276
332
|
for submobject in self.submobjects:
|
|
277
333
|
submobject.set_stroke(color, width, opacity, background, family)
|
|
@@ -289,29 +345,60 @@ class VMobject(Mobject):
|
|
|
289
345
|
if opacity is not None:
|
|
290
346
|
setattr(self, opacity_name, opacity)
|
|
291
347
|
if color is not None and background:
|
|
292
|
-
|
|
348
|
+
if isinstance(color, (list, tuple)):
|
|
349
|
+
self.background_stroke_color = ManimColor.parse(color)
|
|
350
|
+
else:
|
|
351
|
+
self.background_stroke_color = ManimColor(color)
|
|
293
352
|
return self
|
|
294
353
|
|
|
295
|
-
def
|
|
354
|
+
def set_cap_style(self, cap_style: CapStyleType) -> Self:
|
|
355
|
+
"""
|
|
356
|
+
Sets the cap style of the :class:`VMobject`.
|
|
357
|
+
|
|
358
|
+
Parameters
|
|
359
|
+
----------
|
|
360
|
+
cap_style
|
|
361
|
+
The cap style to be set. See :class:`.CapStyleType` for options.
|
|
362
|
+
|
|
363
|
+
Returns
|
|
364
|
+
-------
|
|
365
|
+
:class:`VMobject`
|
|
366
|
+
``self``
|
|
367
|
+
|
|
368
|
+
Examples
|
|
369
|
+
--------
|
|
370
|
+
.. manim:: CapStyleExample
|
|
371
|
+
:save_last_frame:
|
|
372
|
+
|
|
373
|
+
class CapStyleExample(Scene):
|
|
374
|
+
def construct(self):
|
|
375
|
+
line = Line(LEFT, RIGHT, color=YELLOW, stroke_width=20)
|
|
376
|
+
line.set_cap_style(CapStyleType.ROUND)
|
|
377
|
+
self.add(line)
|
|
378
|
+
"""
|
|
379
|
+
self.cap_style = cap_style
|
|
380
|
+
return self
|
|
381
|
+
|
|
382
|
+
def set_background_stroke(self, **kwargs) -> Self:
|
|
296
383
|
kwargs["background"] = True
|
|
297
384
|
self.set_stroke(**kwargs)
|
|
298
385
|
return self
|
|
299
386
|
|
|
300
387
|
def set_style(
|
|
301
388
|
self,
|
|
302
|
-
fill_color=None,
|
|
303
|
-
fill_opacity=None,
|
|
304
|
-
stroke_color=None,
|
|
305
|
-
stroke_width=None,
|
|
306
|
-
stroke_opacity=None,
|
|
307
|
-
background_stroke_color=None,
|
|
308
|
-
background_stroke_width=None,
|
|
309
|
-
background_stroke_opacity=None,
|
|
310
|
-
sheen_factor=None,
|
|
311
|
-
sheen_direction=None,
|
|
312
|
-
background_image=None,
|
|
313
|
-
family=True,
|
|
314
|
-
):
|
|
389
|
+
fill_color: ParsableManimColor | None = None,
|
|
390
|
+
fill_opacity: float | None = None,
|
|
391
|
+
stroke_color: ParsableManimColor | None = None,
|
|
392
|
+
stroke_width: float | None = None,
|
|
393
|
+
stroke_opacity: float | None = None,
|
|
394
|
+
background_stroke_color: ParsableManimColor | None = None,
|
|
395
|
+
background_stroke_width: float | None = None,
|
|
396
|
+
background_stroke_opacity: float | None = None,
|
|
397
|
+
sheen_factor: float | None = None,
|
|
398
|
+
sheen_direction: Vector3DLike | None = None,
|
|
399
|
+
background_image: Image | str | None = None,
|
|
400
|
+
family: bool = True,
|
|
401
|
+
) -> Self:
|
|
315
402
|
self.set_fill(color=fill_color, opacity=fill_opacity, family=family)
|
|
316
403
|
self.set_stroke(
|
|
317
404
|
color=stroke_color,
|
|
@@ -335,16 +422,17 @@ class VMobject(Mobject):
|
|
|
335
422
|
self.color_using_background_image(background_image)
|
|
336
423
|
return self
|
|
337
424
|
|
|
338
|
-
def get_style(self, simple=False):
|
|
425
|
+
def get_style(self, simple: bool = False) -> dict:
|
|
339
426
|
ret = {
|
|
340
427
|
"stroke_opacity": self.get_stroke_opacity(),
|
|
341
428
|
"stroke_width": self.get_stroke_width(),
|
|
342
429
|
}
|
|
343
430
|
|
|
431
|
+
# TODO: FIX COLORS HERE
|
|
344
432
|
if simple:
|
|
345
|
-
ret["fill_color"] =
|
|
433
|
+
ret["fill_color"] = self.get_fill_color()
|
|
346
434
|
ret["fill_opacity"] = self.get_fill_opacity()
|
|
347
|
-
ret["stroke_color"] =
|
|
435
|
+
ret["stroke_color"] = self.get_stroke_color()
|
|
348
436
|
else:
|
|
349
437
|
ret["fill_color"] = self.get_fill_colors()
|
|
350
438
|
ret["fill_opacity"] = self.get_fill_opacities()
|
|
@@ -358,7 +446,7 @@ class VMobject(Mobject):
|
|
|
358
446
|
|
|
359
447
|
return ret
|
|
360
448
|
|
|
361
|
-
def match_style(self, vmobject, family=True):
|
|
449
|
+
def match_style(self, vmobject: VMobject, family: bool = True) -> Self:
|
|
362
450
|
self.set_style(**vmobject.get_style(), family=False)
|
|
363
451
|
|
|
364
452
|
if family:
|
|
@@ -373,18 +461,83 @@ class VMobject(Mobject):
|
|
|
373
461
|
sm1.match_style(sm2)
|
|
374
462
|
return self
|
|
375
463
|
|
|
376
|
-
def set_color(self, color, family=True):
|
|
464
|
+
def set_color(self, color: ParsableManimColor, family: bool = True) -> Self:
|
|
377
465
|
self.set_fill(color, family=family)
|
|
378
466
|
self.set_stroke(color, family=family)
|
|
379
467
|
return self
|
|
380
468
|
|
|
381
|
-
def set_opacity(self, opacity, family=True):
|
|
469
|
+
def set_opacity(self, opacity: float, family: bool = True) -> Self:
|
|
382
470
|
self.set_fill(opacity=opacity, family=family)
|
|
383
471
|
self.set_stroke(opacity=opacity, family=family)
|
|
384
472
|
self.set_stroke(opacity=opacity, family=family, background=True)
|
|
385
473
|
return self
|
|
386
474
|
|
|
387
|
-
def
|
|
475
|
+
def scale(
|
|
476
|
+
self,
|
|
477
|
+
scale_factor: float,
|
|
478
|
+
scale_stroke: bool = False,
|
|
479
|
+
*,
|
|
480
|
+
about_point: Point3DLike | None = None,
|
|
481
|
+
about_edge: Vector3DLike | None = None,
|
|
482
|
+
) -> Self:
|
|
483
|
+
r"""Scale the size by a factor.
|
|
484
|
+
|
|
485
|
+
Default behavior is to scale about the center of the vmobject.
|
|
486
|
+
|
|
487
|
+
Parameters
|
|
488
|
+
----------
|
|
489
|
+
scale_factor
|
|
490
|
+
The scaling factor :math:`\alpha`. If :math:`0 < |\alpha| < 1`, the mobject
|
|
491
|
+
will shrink, and for :math:`|\alpha| > 1` it will grow. Furthermore,
|
|
492
|
+
if :math:`\alpha < 0`, the mobject is also flipped.
|
|
493
|
+
scale_stroke
|
|
494
|
+
Boolean determining if the object's outline is scaled when the object is scaled.
|
|
495
|
+
If enabled, and object with 2px outline is scaled by a factor of .5, it will have an outline of 1px.
|
|
496
|
+
kwargs
|
|
497
|
+
Additional keyword arguments passed to
|
|
498
|
+
:meth:`~.Mobject.scale`.
|
|
499
|
+
|
|
500
|
+
Returns
|
|
501
|
+
-------
|
|
502
|
+
:class:`VMobject`
|
|
503
|
+
``self``
|
|
504
|
+
|
|
505
|
+
Examples
|
|
506
|
+
--------
|
|
507
|
+
|
|
508
|
+
.. manim:: MobjectScaleExample
|
|
509
|
+
:save_last_frame:
|
|
510
|
+
|
|
511
|
+
class MobjectScaleExample(Scene):
|
|
512
|
+
def construct(self):
|
|
513
|
+
c1 = Circle(1, RED).set_x(-1)
|
|
514
|
+
c2 = Circle(1, GREEN).set_x(1)
|
|
515
|
+
|
|
516
|
+
vg = VGroup(c1, c2)
|
|
517
|
+
vg.set_stroke(width=50)
|
|
518
|
+
self.add(vg)
|
|
519
|
+
|
|
520
|
+
self.play(
|
|
521
|
+
c1.animate.scale(.25),
|
|
522
|
+
c2.animate.scale(.25,
|
|
523
|
+
scale_stroke=True)
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
See also
|
|
527
|
+
--------
|
|
528
|
+
:meth:`move_to`
|
|
529
|
+
|
|
530
|
+
"""
|
|
531
|
+
if scale_stroke:
|
|
532
|
+
self.set_stroke(width=abs(scale_factor) * self.get_stroke_width())
|
|
533
|
+
self.set_stroke(
|
|
534
|
+
width=abs(scale_factor) * self.get_stroke_width(background=True),
|
|
535
|
+
background=True,
|
|
536
|
+
)
|
|
537
|
+
super().scale(scale_factor, about_point=about_point, about_edge=about_edge)
|
|
538
|
+
return self
|
|
539
|
+
|
|
540
|
+
def fade(self, darkness: float = 0.5, family: bool = True) -> Self:
|
|
388
541
|
factor = 1.0 - darkness
|
|
389
542
|
self.set_fill(opacity=factor * self.get_fill_opacity(), family=False)
|
|
390
543
|
self.set_stroke(opacity=factor * self.get_stroke_opacity(), family=False)
|
|
@@ -395,13 +548,13 @@ class VMobject(Mobject):
|
|
|
395
548
|
super().fade(darkness, family)
|
|
396
549
|
return self
|
|
397
550
|
|
|
398
|
-
def get_fill_rgbas(self):
|
|
551
|
+
def get_fill_rgbas(self) -> FloatRGBA_Array:
|
|
399
552
|
try:
|
|
400
553
|
return self.fill_rgbas
|
|
401
554
|
except AttributeError:
|
|
402
555
|
return np.zeros((1, 4))
|
|
403
556
|
|
|
404
|
-
def get_fill_color(self):
|
|
557
|
+
def get_fill_color(self) -> ManimColor:
|
|
405
558
|
"""
|
|
406
559
|
If there are multiple colors (for gradient)
|
|
407
560
|
this returns the first one
|
|
@@ -410,66 +563,70 @@ class VMobject(Mobject):
|
|
|
410
563
|
|
|
411
564
|
fill_color = property(get_fill_color, set_fill)
|
|
412
565
|
|
|
413
|
-
def get_fill_opacity(self):
|
|
566
|
+
def get_fill_opacity(self) -> ManimFloat:
|
|
414
567
|
"""
|
|
415
568
|
If there are multiple opacities, this returns the
|
|
416
569
|
first
|
|
417
570
|
"""
|
|
418
571
|
return self.get_fill_opacities()[0]
|
|
419
572
|
|
|
420
|
-
|
|
573
|
+
# TODO: Does this just do a copy?
|
|
574
|
+
# TODO: I have the feeling that this function should not return None, does that have any usage ?
|
|
575
|
+
def get_fill_colors(self) -> list[ManimColor | None]:
|
|
421
576
|
return [
|
|
422
|
-
|
|
577
|
+
ManimColor(rgba[:3]) if rgba.any() else None
|
|
423
578
|
for rgba in self.get_fill_rgbas()
|
|
424
579
|
]
|
|
425
580
|
|
|
426
|
-
def get_fill_opacities(self):
|
|
581
|
+
def get_fill_opacities(self) -> npt.NDArray[ManimFloat]:
|
|
427
582
|
return self.get_fill_rgbas()[:, 3]
|
|
428
583
|
|
|
429
|
-
def get_stroke_rgbas(self, background=False):
|
|
584
|
+
def get_stroke_rgbas(self, background: bool = False) -> FloatRGBA_Array:
|
|
430
585
|
try:
|
|
431
586
|
if background:
|
|
587
|
+
self.background_stroke_rgbas: FloatRGBA_Array
|
|
432
588
|
rgbas = self.background_stroke_rgbas
|
|
433
589
|
else:
|
|
590
|
+
self.stroke_rgbas: FloatRGBA_Array
|
|
434
591
|
rgbas = self.stroke_rgbas
|
|
435
592
|
return rgbas
|
|
436
593
|
except AttributeError:
|
|
437
594
|
return np.zeros((1, 4))
|
|
438
595
|
|
|
439
|
-
def get_stroke_color(self, background=False):
|
|
596
|
+
def get_stroke_color(self, background: bool = False) -> ManimColor | None:
|
|
440
597
|
return self.get_stroke_colors(background)[0]
|
|
441
598
|
|
|
442
599
|
stroke_color = property(get_stroke_color, set_stroke)
|
|
443
600
|
|
|
444
|
-
def get_stroke_width(self, background=False):
|
|
601
|
+
def get_stroke_width(self, background: bool = False) -> float:
|
|
445
602
|
if background:
|
|
446
603
|
width = self.background_stroke_width
|
|
447
604
|
else:
|
|
448
605
|
width = self.stroke_width
|
|
449
606
|
if isinstance(width, str):
|
|
450
607
|
width = int(width)
|
|
451
|
-
return max(0, width)
|
|
608
|
+
return max(0.0, width)
|
|
452
609
|
|
|
453
|
-
def get_stroke_opacity(self, background=False):
|
|
610
|
+
def get_stroke_opacity(self, background: bool = False) -> ManimFloat:
|
|
454
611
|
return self.get_stroke_opacities(background)[0]
|
|
455
612
|
|
|
456
|
-
def get_stroke_colors(self, background=False):
|
|
613
|
+
def get_stroke_colors(self, background: bool = False) -> list[ManimColor | None]:
|
|
457
614
|
return [
|
|
458
|
-
|
|
615
|
+
ManimColor(rgba[:3]) if rgba.any() else None
|
|
459
616
|
for rgba in self.get_stroke_rgbas(background)
|
|
460
617
|
]
|
|
461
618
|
|
|
462
|
-
def get_stroke_opacities(self, background=False):
|
|
619
|
+
def get_stroke_opacities(self, background: bool = False) -> npt.NDArray[ManimFloat]:
|
|
463
620
|
return self.get_stroke_rgbas(background)[:, 3]
|
|
464
621
|
|
|
465
|
-
def get_color(self):
|
|
622
|
+
def get_color(self) -> ManimColor:
|
|
466
623
|
if np.all(self.get_fill_opacities() == 0):
|
|
467
624
|
return self.get_stroke_color()
|
|
468
625
|
return self.get_fill_color()
|
|
469
626
|
|
|
470
|
-
color = property(get_color, set_color)
|
|
627
|
+
color: ManimColor = property(get_color, set_color)
|
|
471
628
|
|
|
472
|
-
def set_sheen_direction(self, direction:
|
|
629
|
+
def set_sheen_direction(self, direction: Vector3DLike, family: bool = True) -> Self:
|
|
473
630
|
"""Sets the direction of the applied sheen.
|
|
474
631
|
|
|
475
632
|
Parameters
|
|
@@ -488,16 +645,17 @@ class VMobject(Mobject):
|
|
|
488
645
|
:meth:`~.VMobject.set_sheen`
|
|
489
646
|
:meth:`~.VMobject.rotate_sheen_direction`
|
|
490
647
|
"""
|
|
491
|
-
|
|
492
|
-
direction = np.array(direction)
|
|
648
|
+
direction_copy = np.array(direction)
|
|
493
649
|
if family:
|
|
494
650
|
for submob in self.get_family():
|
|
495
|
-
submob.sheen_direction =
|
|
651
|
+
submob.sheen_direction = direction_copy.copy()
|
|
496
652
|
else:
|
|
497
|
-
self.sheen_direction =
|
|
653
|
+
self.sheen_direction = direction_copy
|
|
498
654
|
return self
|
|
499
655
|
|
|
500
|
-
def rotate_sheen_direction(
|
|
656
|
+
def rotate_sheen_direction(
|
|
657
|
+
self, angle: float, axis: Vector3DLike = OUT, family: bool = True
|
|
658
|
+
) -> Self:
|
|
501
659
|
"""Rotates the direction of the applied sheen.
|
|
502
660
|
|
|
503
661
|
Parameters
|
|
@@ -528,7 +686,9 @@ class VMobject(Mobject):
|
|
|
528
686
|
self.sheen_direction = rotate_vector(self.sheen_direction, angle, axis)
|
|
529
687
|
return self
|
|
530
688
|
|
|
531
|
-
def set_sheen(
|
|
689
|
+
def set_sheen(
|
|
690
|
+
self, factor: float, direction: Vector3DLike | None = None, family: bool = True
|
|
691
|
+
) -> Self:
|
|
532
692
|
"""Applies a color gradient from a direction.
|
|
533
693
|
|
|
534
694
|
Parameters
|
|
@@ -550,11 +710,10 @@ class VMobject(Mobject):
|
|
|
550
710
|
circle = Circle(fill_opacity=1).set_sheen(-0.3, DR)
|
|
551
711
|
self.add(circle)
|
|
552
712
|
"""
|
|
553
|
-
|
|
554
713
|
if family:
|
|
555
714
|
for submob in self.submobjects:
|
|
556
715
|
submob.set_sheen(factor, direction, family)
|
|
557
|
-
self.sheen_factor = factor
|
|
716
|
+
self.sheen_factor: float = factor
|
|
558
717
|
if direction is not None:
|
|
559
718
|
# family set to false because recursion will
|
|
560
719
|
# already be handled above
|
|
@@ -565,13 +724,13 @@ class VMobject(Mobject):
|
|
|
565
724
|
self.set_fill(self.get_fill_color(), family=family)
|
|
566
725
|
return self
|
|
567
726
|
|
|
568
|
-
def get_sheen_direction(self):
|
|
727
|
+
def get_sheen_direction(self) -> Vector3D:
|
|
569
728
|
return np.array(self.sheen_direction)
|
|
570
729
|
|
|
571
|
-
def get_sheen_factor(self):
|
|
730
|
+
def get_sheen_factor(self) -> float:
|
|
572
731
|
return self.sheen_factor
|
|
573
732
|
|
|
574
|
-
def get_gradient_start_and_end_points(self):
|
|
733
|
+
def get_gradient_start_and_end_points(self) -> tuple[Point3D, Point3D]:
|
|
575
734
|
if self.shade_in_3d:
|
|
576
735
|
return get_3d_vmob_gradient_start_and_end_points(self)
|
|
577
736
|
else:
|
|
@@ -583,8 +742,8 @@ class VMobject(Mobject):
|
|
|
583
742
|
offset = np.dot(bases, direction)
|
|
584
743
|
return (c - offset, c + offset)
|
|
585
744
|
|
|
586
|
-
def color_using_background_image(self, background_image: Image | str):
|
|
587
|
-
self.background_image = background_image
|
|
745
|
+
def color_using_background_image(self, background_image: Image | str) -> Self:
|
|
746
|
+
self.background_image: Image | str = background_image
|
|
588
747
|
self.set_color(WHITE)
|
|
589
748
|
for submob in self.submobjects:
|
|
590
749
|
submob.color_using_background_image(background_image)
|
|
@@ -593,26 +752,28 @@ class VMobject(Mobject):
|
|
|
593
752
|
def get_background_image(self) -> Image | str:
|
|
594
753
|
return self.background_image
|
|
595
754
|
|
|
596
|
-
def match_background_image(self, vmobject):
|
|
755
|
+
def match_background_image(self, vmobject: VMobject) -> Self:
|
|
597
756
|
self.color_using_background_image(vmobject.get_background_image())
|
|
598
757
|
return self
|
|
599
758
|
|
|
600
|
-
def set_shade_in_3d(
|
|
759
|
+
def set_shade_in_3d(
|
|
760
|
+
self, value: bool = True, z_index_as_group: bool = False
|
|
761
|
+
) -> Self:
|
|
601
762
|
for submob in self.get_family():
|
|
602
763
|
submob.shade_in_3d = value
|
|
603
764
|
if z_index_as_group:
|
|
604
765
|
submob.z_index_group = self
|
|
605
766
|
return self
|
|
606
767
|
|
|
607
|
-
def set_points(self, points):
|
|
608
|
-
self.points = np.array(points)
|
|
768
|
+
def set_points(self, points: Point3DLike_Array) -> Self:
|
|
769
|
+
self.points: Point3D_Array = np.array(points)
|
|
609
770
|
return self
|
|
610
771
|
|
|
611
772
|
def resize_points(
|
|
612
773
|
self,
|
|
613
774
|
new_length: int,
|
|
614
|
-
resize_func: Callable[[
|
|
615
|
-
):
|
|
775
|
+
resize_func: Callable[[Point3D_Array, int], Point3D_Array] = resize_array,
|
|
776
|
+
) -> Self:
|
|
616
777
|
"""Resize the array of anchor points and handles to have
|
|
617
778
|
the specified size.
|
|
618
779
|
|
|
@@ -631,11 +792,11 @@ class VMobject(Mobject):
|
|
|
631
792
|
|
|
632
793
|
def set_anchors_and_handles(
|
|
633
794
|
self,
|
|
634
|
-
anchors1:
|
|
635
|
-
handles1:
|
|
636
|
-
handles2:
|
|
637
|
-
anchors2:
|
|
638
|
-
):
|
|
795
|
+
anchors1: Point3DLike_Array,
|
|
796
|
+
handles1: Point3DLike_Array,
|
|
797
|
+
handles2: Point3DLike_Array,
|
|
798
|
+
anchors2: Point3DLike_Array,
|
|
799
|
+
) -> Self:
|
|
639
800
|
"""Given two sets of anchors and handles, process them to set them as anchors
|
|
640
801
|
and handles of the VMobject.
|
|
641
802
|
|
|
@@ -652,7 +813,7 @@ class VMobject(Mobject):
|
|
|
652
813
|
assert len(anchors1) == len(handles1) == len(handles2) == len(anchors2)
|
|
653
814
|
nppcc = self.n_points_per_cubic_curve # 4
|
|
654
815
|
total_len = nppcc * len(anchors1)
|
|
655
|
-
self.points = np.
|
|
816
|
+
self.points = np.empty((total_len, self.dim))
|
|
656
817
|
# the following will, from the four sets, dispatch them in points such that
|
|
657
818
|
# self.points = [
|
|
658
819
|
# anchors1[0], handles1[0], handles2[0], anchors1[0], anchors1[1],
|
|
@@ -663,45 +824,84 @@ class VMobject(Mobject):
|
|
|
663
824
|
self.points[index::nppcc] = array
|
|
664
825
|
return self
|
|
665
826
|
|
|
666
|
-
def clear_points(self):
|
|
827
|
+
def clear_points(self) -> None:
|
|
828
|
+
# TODO: shouldn't this return self instead of None?
|
|
667
829
|
self.points = np.zeros((0, self.dim))
|
|
668
830
|
|
|
669
|
-
def append_points(self, new_points):
|
|
831
|
+
def append_points(self, new_points: Point3DLike_Array) -> Self:
|
|
832
|
+
"""Append the given ``new_points`` to the end of
|
|
833
|
+
:attr:`VMobject.points`.
|
|
834
|
+
|
|
835
|
+
Parameters
|
|
836
|
+
----------
|
|
837
|
+
new_points
|
|
838
|
+
An array of 3D points to append.
|
|
839
|
+
|
|
840
|
+
Returns
|
|
841
|
+
-------
|
|
842
|
+
:class:`VMobject`
|
|
843
|
+
The VMobject itself, after appending ``new_points``.
|
|
844
|
+
"""
|
|
670
845
|
# TODO, check that number new points is a multiple of 4?
|
|
671
846
|
# or else that if len(self.points) % 4 == 1, then
|
|
672
847
|
# len(new_points) % 4 == 3?
|
|
673
|
-
|
|
848
|
+
n = len(self.points)
|
|
849
|
+
points = np.empty((n + len(new_points), self.dim))
|
|
850
|
+
points[:n] = self.points
|
|
851
|
+
points[n:] = new_points
|
|
852
|
+
self.points = points
|
|
674
853
|
return self
|
|
675
854
|
|
|
676
|
-
def start_new_path(self, point):
|
|
677
|
-
|
|
855
|
+
def start_new_path(self, point: Point3DLike) -> Self:
|
|
856
|
+
"""Append a ``point`` to the :attr:`VMobject.points`, which will be the
|
|
857
|
+
beginning of a new Bézier curve in the path given by the points. If
|
|
858
|
+
there's an unfinished curve at the end of :attr:`VMobject.points`,
|
|
859
|
+
complete it by appending the last Bézier curve's start anchor as many
|
|
860
|
+
times as needed.
|
|
861
|
+
|
|
862
|
+
Parameters
|
|
863
|
+
----------
|
|
864
|
+
point
|
|
865
|
+
A 3D point to append to :attr:`VMobject.points`.
|
|
866
|
+
|
|
867
|
+
Returns
|
|
868
|
+
-------
|
|
869
|
+
:class:`VMobject`
|
|
870
|
+
The VMobject itself, after appending ``point`` and starting a new
|
|
871
|
+
curve.
|
|
872
|
+
"""
|
|
873
|
+
n_points = len(self.points)
|
|
874
|
+
nppc = self.n_points_per_curve
|
|
875
|
+
if n_points % nppc != 0:
|
|
678
876
|
# close the open path by appending the last
|
|
679
877
|
# start anchor sufficiently often
|
|
680
878
|
last_anchor = self.get_start_anchors()[-1]
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
879
|
+
closure = [last_anchor] * (nppc - (n_points % nppc))
|
|
880
|
+
self.append_points(closure + [point])
|
|
881
|
+
else:
|
|
882
|
+
self.append_points([point])
|
|
684
883
|
return self
|
|
685
884
|
|
|
686
885
|
def add_cubic_bezier_curve(
|
|
687
886
|
self,
|
|
688
|
-
anchor1:
|
|
689
|
-
handle1:
|
|
690
|
-
handle2:
|
|
691
|
-
anchor2,
|
|
887
|
+
anchor1: Point3DLike,
|
|
888
|
+
handle1: Point3DLike,
|
|
889
|
+
handle2: Point3DLike,
|
|
890
|
+
anchor2: Point3DLike,
|
|
692
891
|
) -> None:
|
|
693
892
|
# TODO, check the len(self.points) % 4 == 0?
|
|
694
893
|
self.append_points([anchor1, handle1, handle2, anchor2])
|
|
695
894
|
|
|
696
|
-
|
|
895
|
+
# what type is curves?
|
|
896
|
+
def add_cubic_bezier_curves(self, curves) -> None:
|
|
697
897
|
self.append_points(curves.flatten())
|
|
698
898
|
|
|
699
899
|
def add_cubic_bezier_curve_to(
|
|
700
900
|
self,
|
|
701
|
-
handle1:
|
|
702
|
-
handle2:
|
|
703
|
-
anchor:
|
|
704
|
-
):
|
|
901
|
+
handle1: Point3DLike,
|
|
902
|
+
handle2: Point3DLike,
|
|
903
|
+
anchor: Point3DLike,
|
|
904
|
+
) -> Self:
|
|
705
905
|
"""Add cubic bezier curve to the path.
|
|
706
906
|
|
|
707
907
|
NOTE : the first anchor is not a parameter as by default the end of the last sub-path!
|
|
@@ -730,9 +930,9 @@ class VMobject(Mobject):
|
|
|
730
930
|
|
|
731
931
|
def add_quadratic_bezier_curve_to(
|
|
732
932
|
self,
|
|
733
|
-
handle:
|
|
734
|
-
anchor:
|
|
735
|
-
):
|
|
933
|
+
handle: Point3DLike,
|
|
934
|
+
anchor: Point3DLike,
|
|
935
|
+
) -> Self:
|
|
736
936
|
"""Add Quadratic bezier curve to the path.
|
|
737
937
|
|
|
738
938
|
Returns
|
|
@@ -754,30 +954,29 @@ class VMobject(Mobject):
|
|
|
754
954
|
)
|
|
755
955
|
return self
|
|
756
956
|
|
|
757
|
-
def add_line_to(self, point:
|
|
957
|
+
def add_line_to(self, point: Point3DLike) -> Self:
|
|
758
958
|
"""Add a straight line from the last point of VMobject to the given point.
|
|
759
959
|
|
|
760
960
|
Parameters
|
|
761
961
|
----------
|
|
762
962
|
|
|
763
963
|
point
|
|
764
|
-
end of the straight line.
|
|
964
|
+
The end of the straight line.
|
|
765
965
|
|
|
766
966
|
Returns
|
|
767
967
|
-------
|
|
768
968
|
:class:`VMobject`
|
|
769
969
|
``self``
|
|
770
970
|
"""
|
|
771
|
-
nppcc = self.n_points_per_cubic_curve
|
|
772
971
|
self.add_cubic_bezier_curve_to(
|
|
773
972
|
*(
|
|
774
|
-
interpolate(self.get_last_point(), point,
|
|
775
|
-
for
|
|
973
|
+
interpolate(self.get_last_point(), point, t)
|
|
974
|
+
for t in self._bezier_t_values[1:]
|
|
776
975
|
)
|
|
777
976
|
)
|
|
778
977
|
return self
|
|
779
978
|
|
|
780
|
-
def add_smooth_curve_to(self, *points:
|
|
979
|
+
def add_smooth_curve_to(self, *points: Point3DLike) -> Self:
|
|
781
980
|
"""Creates a smooth curve from given points and add it to the VMobject. If two points are passed in, the first is interpreted
|
|
782
981
|
as a handle, the second as an anchor.
|
|
783
982
|
|
|
@@ -820,32 +1019,73 @@ class VMobject(Mobject):
|
|
|
820
1019
|
self.append_points([last_a2, handle1, handle2, new_anchor])
|
|
821
1020
|
return self
|
|
822
1021
|
|
|
823
|
-
def has_new_path_started(self):
|
|
1022
|
+
def has_new_path_started(self) -> bool:
|
|
824
1023
|
nppcc = self.n_points_per_cubic_curve # 4
|
|
825
1024
|
# A new path starting is defined by a control point which is not part of a bezier subcurve.
|
|
826
1025
|
return len(self.points) % nppcc == 1
|
|
827
1026
|
|
|
828
|
-
def get_last_point(self):
|
|
1027
|
+
def get_last_point(self) -> Point3D:
|
|
829
1028
|
return self.points[-1]
|
|
830
1029
|
|
|
831
|
-
def is_closed(self):
|
|
1030
|
+
def is_closed(self) -> bool:
|
|
832
1031
|
# TODO use consider_points_equals_2d ?
|
|
833
1032
|
return self.consider_points_equals(self.points[0], self.points[-1])
|
|
834
1033
|
|
|
835
|
-
def close_path(self):
|
|
1034
|
+
def close_path(self) -> None:
|
|
836
1035
|
if not self.is_closed():
|
|
837
1036
|
self.add_line_to(self.get_subpaths()[-1][0])
|
|
838
1037
|
|
|
839
|
-
def add_points_as_corners(self, points:
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
1038
|
+
def add_points_as_corners(self, points: Point3DLike_Array) -> Self:
|
|
1039
|
+
"""Append multiple straight lines at the end of
|
|
1040
|
+
:attr:`VMobject.points`, which connect the given ``points`` in order
|
|
1041
|
+
starting from the end of the current path. These ``points`` would be
|
|
1042
|
+
therefore the corners of the new polyline appended to the path.
|
|
1043
|
+
|
|
1044
|
+
Parameters
|
|
1045
|
+
----------
|
|
1046
|
+
points
|
|
1047
|
+
An array of 3D points representing the corners of the polyline to
|
|
1048
|
+
append to :attr:`VMobject.points`.
|
|
843
1049
|
|
|
844
|
-
|
|
845
|
-
|
|
1050
|
+
Returns
|
|
1051
|
+
-------
|
|
1052
|
+
:class:`VMobject`
|
|
1053
|
+
The VMobject itself, after appending the straight lines to its
|
|
1054
|
+
path.
|
|
1055
|
+
"""
|
|
1056
|
+
self.throw_error_if_no_points()
|
|
1057
|
+
|
|
1058
|
+
points = np.asarray(points).reshape(-1, self.dim)
|
|
1059
|
+
num_points = points.shape[0]
|
|
1060
|
+
if num_points == 0:
|
|
1061
|
+
return self
|
|
846
1062
|
|
|
847
|
-
|
|
848
|
-
|
|
1063
|
+
start_corners = np.empty((num_points, self.dim))
|
|
1064
|
+
start_corners[0] = self.points[-1]
|
|
1065
|
+
start_corners[1:] = points[:-1]
|
|
1066
|
+
end_corners = points
|
|
1067
|
+
|
|
1068
|
+
if self.has_new_path_started():
|
|
1069
|
+
# Remove the last point from the new path
|
|
1070
|
+
self.points = self.points[:-1]
|
|
1071
|
+
|
|
1072
|
+
nppcc = self.n_points_per_cubic_curve
|
|
1073
|
+
new_points = np.empty((nppcc * start_corners.shape[0], self.dim))
|
|
1074
|
+
new_points[::nppcc] = start_corners
|
|
1075
|
+
new_points[nppcc - 1 :: nppcc] = end_corners
|
|
1076
|
+
for i, t in enumerate(self._bezier_t_values):
|
|
1077
|
+
new_points[i::nppcc] = interpolate(start_corners, end_corners, t)
|
|
1078
|
+
|
|
1079
|
+
self.append_points(new_points)
|
|
1080
|
+
return self
|
|
1081
|
+
|
|
1082
|
+
def set_points_as_corners(self, points: Point3DLike_Array) -> Self:
|
|
1083
|
+
"""Given an array of points, set them as corners of the
|
|
1084
|
+
:class:`VMobject`.
|
|
1085
|
+
|
|
1086
|
+
To achieve that, this algorithm sets handles aligned with the anchors
|
|
1087
|
+
such that the resultant Bézier curve will be the segment between the
|
|
1088
|
+
two anchors.
|
|
849
1089
|
|
|
850
1090
|
Parameters
|
|
851
1091
|
----------
|
|
@@ -855,23 +1095,43 @@ class VMobject(Mobject):
|
|
|
855
1095
|
Returns
|
|
856
1096
|
-------
|
|
857
1097
|
:class:`VMobject`
|
|
858
|
-
|
|
1098
|
+
The VMobject itself, after setting the new points as corners.
|
|
1099
|
+
|
|
1100
|
+
|
|
1101
|
+
Examples
|
|
1102
|
+
--------
|
|
1103
|
+
.. manim:: PointsAsCornersExample
|
|
1104
|
+
:save_last_frame:
|
|
1105
|
+
|
|
1106
|
+
class PointsAsCornersExample(Scene):
|
|
1107
|
+
def construct(self):
|
|
1108
|
+
corners = (
|
|
1109
|
+
# create square
|
|
1110
|
+
UR, UL,
|
|
1111
|
+
DL, DR,
|
|
1112
|
+
UR,
|
|
1113
|
+
# create crosses
|
|
1114
|
+
DL, UL,
|
|
1115
|
+
DR
|
|
1116
|
+
)
|
|
1117
|
+
vmob = VMobject(stroke_color=RED)
|
|
1118
|
+
vmob.set_points_as_corners(corners).scale(2)
|
|
1119
|
+
self.add(vmob)
|
|
859
1120
|
"""
|
|
860
|
-
nppcc = self.n_points_per_cubic_curve
|
|
861
1121
|
points = np.array(points)
|
|
862
1122
|
# This will set the handles aligned with the anchors.
|
|
863
1123
|
# Id est, a bezier curve will be the segment from the two anchors such that the handles belongs to this segment.
|
|
864
1124
|
self.set_anchors_and_handles(
|
|
865
|
-
*(interpolate(points[:-1], points[1:],
|
|
1125
|
+
*(interpolate(points[:-1], points[1:], t) for t in self._bezier_t_values)
|
|
866
1126
|
)
|
|
867
1127
|
return self
|
|
868
1128
|
|
|
869
|
-
def set_points_smoothly(self, points):
|
|
1129
|
+
def set_points_smoothly(self, points: Point3DLike_Array) -> Self:
|
|
870
1130
|
self.set_points_as_corners(points)
|
|
871
1131
|
self.make_smooth()
|
|
872
1132
|
return self
|
|
873
1133
|
|
|
874
|
-
def change_anchor_mode(self, mode:
|
|
1134
|
+
def change_anchor_mode(self, mode: Literal["jagged", "smooth"]) -> Self:
|
|
875
1135
|
"""Changes the anchor mode of the bezier curves. This will modify the handles.
|
|
876
1136
|
|
|
877
1137
|
There can be only two modes, "jagged", and "smooth".
|
|
@@ -881,7 +1141,7 @@ class VMobject(Mobject):
|
|
|
881
1141
|
:class:`VMobject`
|
|
882
1142
|
``self``
|
|
883
1143
|
"""
|
|
884
|
-
assert mode in ["jagged", "smooth"]
|
|
1144
|
+
assert mode in ["jagged", "smooth"], 'mode must be either "jagged" or "smooth"'
|
|
885
1145
|
nppcc = self.n_points_per_cubic_curve
|
|
886
1146
|
for submob in self.family_members_with_points():
|
|
887
1147
|
subpaths = submob.get_subpaths()
|
|
@@ -892,8 +1152,8 @@ class VMobject(Mobject):
|
|
|
892
1152
|
# The append is needed as the last element is not reached when slicing with numpy.
|
|
893
1153
|
anchors = np.append(subpath[::nppcc], subpath[-1:], 0)
|
|
894
1154
|
if mode == "smooth":
|
|
895
|
-
h1, h2 =
|
|
896
|
-
|
|
1155
|
+
h1, h2 = get_smooth_cubic_bezier_handle_points(anchors)
|
|
1156
|
+
else: # mode == "jagged"
|
|
897
1157
|
# The following will make the handles aligned with the anchors, thus making the bezier curve a segment
|
|
898
1158
|
a1 = anchors[:-1]
|
|
899
1159
|
a2 = anchors[1:]
|
|
@@ -905,27 +1165,31 @@ class VMobject(Mobject):
|
|
|
905
1165
|
submob.append_points(new_subpath)
|
|
906
1166
|
return self
|
|
907
1167
|
|
|
908
|
-
def make_smooth(self):
|
|
1168
|
+
def make_smooth(self) -> Self:
|
|
909
1169
|
return self.change_anchor_mode("smooth")
|
|
910
1170
|
|
|
911
|
-
def make_jagged(self):
|
|
1171
|
+
def make_jagged(self) -> Self:
|
|
912
1172
|
return self.change_anchor_mode("jagged")
|
|
913
1173
|
|
|
914
|
-
def add_subpath(self, points:
|
|
1174
|
+
def add_subpath(self, points: CubicBezierPathLike) -> Self:
|
|
915
1175
|
assert len(points) % 4 == 0
|
|
916
|
-
self.
|
|
1176
|
+
self.append_points(points)
|
|
917
1177
|
return self
|
|
918
1178
|
|
|
919
|
-
def append_vectorized_mobject(self, vectorized_mobject):
|
|
920
|
-
new_points = list(vectorized_mobject.points)
|
|
921
|
-
|
|
1179
|
+
def append_vectorized_mobject(self, vectorized_mobject: VMobject) -> None:
|
|
922
1180
|
if self.has_new_path_started():
|
|
923
1181
|
# Remove last point, which is starting
|
|
924
1182
|
# a new path
|
|
925
1183
|
self.points = self.points[:-1]
|
|
926
|
-
self.append_points(
|
|
1184
|
+
self.append_points(vectorized_mobject.points)
|
|
927
1185
|
|
|
928
|
-
def apply_function(
|
|
1186
|
+
def apply_function(
|
|
1187
|
+
self,
|
|
1188
|
+
function: MappingFunction,
|
|
1189
|
+
*,
|
|
1190
|
+
about_point: Point3DLike | None = None,
|
|
1191
|
+
about_edge: Vector3DLike | None = None,
|
|
1192
|
+
) -> Self:
|
|
929
1193
|
factor = self.pre_function_handle_to_anchor_scale_factor
|
|
930
1194
|
self.scale_handle_to_anchor_distances(factor)
|
|
931
1195
|
super().apply_function(function)
|
|
@@ -937,15 +1201,16 @@ class VMobject(Mobject):
|
|
|
937
1201
|
def rotate(
|
|
938
1202
|
self,
|
|
939
1203
|
angle: float,
|
|
940
|
-
axis:
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1204
|
+
axis: Vector3DLike = OUT,
|
|
1205
|
+
*,
|
|
1206
|
+
about_point: Point3DLike | None = None,
|
|
1207
|
+
about_edge: Vector3DLike | None = None,
|
|
1208
|
+
) -> Self:
|
|
944
1209
|
self.rotate_sheen_direction(angle, axis)
|
|
945
|
-
super().rotate(angle, axis, about_point,
|
|
1210
|
+
super().rotate(angle, axis, about_point=about_point, about_edge=about_edge)
|
|
946
1211
|
return self
|
|
947
1212
|
|
|
948
|
-
def scale_handle_to_anchor_distances(self, factor: float):
|
|
1213
|
+
def scale_handle_to_anchor_distances(self, factor: float) -> Self:
|
|
949
1214
|
"""If the distance between a given handle point H and its associated
|
|
950
1215
|
anchor point A is d, then it changes H to be a distances factor*d
|
|
951
1216
|
away from A, but so that the line from A to H doesn't change.
|
|
@@ -977,10 +1242,10 @@ class VMobject(Mobject):
|
|
|
977
1242
|
return self
|
|
978
1243
|
|
|
979
1244
|
#
|
|
980
|
-
def consider_points_equals(self, p0, p1):
|
|
1245
|
+
def consider_points_equals(self, p0: Point3DLike, p1: Point3DLike) -> bool:
|
|
981
1246
|
return np.allclose(p0, p1, atol=self.tolerance_for_point_equality)
|
|
982
1247
|
|
|
983
|
-
def consider_points_equals_2d(self, p0:
|
|
1248
|
+
def consider_points_equals_2d(self, p0: Point2DLike, p1: Point2DLike) -> bool:
|
|
984
1249
|
"""Determine if two points are close enough to be considered equal.
|
|
985
1250
|
|
|
986
1251
|
This uses the algorithm from np.isclose(), but expanded here for the
|
|
@@ -1001,15 +1266,17 @@ class VMobject(Mobject):
|
|
|
1001
1266
|
atol = self.tolerance_for_point_equality
|
|
1002
1267
|
if abs(p0[0] - p1[0]) > atol + rtol * abs(p1[0]):
|
|
1003
1268
|
return False
|
|
1004
|
-
|
|
1005
|
-
return False
|
|
1006
|
-
return True
|
|
1269
|
+
return abs(p0[1] - p1[1]) <= atol + rtol * abs(p1[1])
|
|
1007
1270
|
|
|
1008
1271
|
# Information about line
|
|
1009
|
-
def get_cubic_bezier_tuples_from_points(
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1272
|
+
def get_cubic_bezier_tuples_from_points(
|
|
1273
|
+
self, points: CubicBezierPathLike
|
|
1274
|
+
) -> CubicBezierPoints_Array:
|
|
1275
|
+
return np.array(self.gen_cubic_bezier_tuples_from_points(points))
|
|
1276
|
+
|
|
1277
|
+
def gen_cubic_bezier_tuples_from_points(
|
|
1278
|
+
self, points: CubicBezierPathLike
|
|
1279
|
+
) -> tuple[CubicBezierPointsLike, ...]:
|
|
1013
1280
|
"""Returns the bezier tuples from an array of points.
|
|
1014
1281
|
|
|
1015
1282
|
self.points is a list of the anchors and handles of the bezier curves of the mobject (ie [anchor1, handle1, handle2, anchor2, anchor3 ..])
|
|
@@ -1024,23 +1291,23 @@ class VMobject(Mobject):
|
|
|
1024
1291
|
|
|
1025
1292
|
Returns
|
|
1026
1293
|
-------
|
|
1027
|
-
|
|
1294
|
+
tuple
|
|
1028
1295
|
Bezier control points.
|
|
1029
1296
|
"""
|
|
1030
1297
|
nppcc = self.n_points_per_cubic_curve
|
|
1031
1298
|
remainder = len(points) % nppcc
|
|
1032
1299
|
points = points[: len(points) - remainder]
|
|
1033
1300
|
# Basically take every nppcc element.
|
|
1034
|
-
return (points[i : i + nppcc] for i in range(0, len(points), nppcc))
|
|
1301
|
+
return tuple(points[i : i + nppcc] for i in range(0, len(points), nppcc))
|
|
1035
1302
|
|
|
1036
|
-
def get_cubic_bezier_tuples(self):
|
|
1303
|
+
def get_cubic_bezier_tuples(self) -> CubicBezierPoints_Array:
|
|
1037
1304
|
return self.get_cubic_bezier_tuples_from_points(self.points)
|
|
1038
1305
|
|
|
1039
1306
|
def _gen_subpaths_from_points(
|
|
1040
1307
|
self,
|
|
1041
|
-
points:
|
|
1042
|
-
filter_func:
|
|
1043
|
-
) ->
|
|
1308
|
+
points: CubicBezierPath,
|
|
1309
|
+
filter_func: Callable[[int], bool],
|
|
1310
|
+
) -> Iterable[CubicSpline]:
|
|
1044
1311
|
"""Given an array of points defining the bezier curves of the vmobject, return subpaths formed by these points.
|
|
1045
1312
|
Here, Two bezier curves form a path if at least two of their anchors are evaluated True by the relation defined by filter_func.
|
|
1046
1313
|
|
|
@@ -1058,7 +1325,7 @@ class VMobject(Mobject):
|
|
|
1058
1325
|
|
|
1059
1326
|
Returns
|
|
1060
1327
|
-------
|
|
1061
|
-
|
|
1328
|
+
Iterable[CubicSpline]
|
|
1062
1329
|
subpaths formed by the points.
|
|
1063
1330
|
"""
|
|
1064
1331
|
nppcc = self.n_points_per_cubic_curve
|
|
@@ -1070,7 +1337,7 @@ class VMobject(Mobject):
|
|
|
1070
1337
|
if (i2 - i1) >= nppcc
|
|
1071
1338
|
)
|
|
1072
1339
|
|
|
1073
|
-
def get_subpaths_from_points(self, points):
|
|
1340
|
+
def get_subpaths_from_points(self, points: CubicBezierPath) -> list[CubicSpline]:
|
|
1074
1341
|
return list(
|
|
1075
1342
|
self._gen_subpaths_from_points(
|
|
1076
1343
|
points,
|
|
@@ -1078,25 +1345,27 @@ class VMobject(Mobject):
|
|
|
1078
1345
|
),
|
|
1079
1346
|
)
|
|
1080
1347
|
|
|
1081
|
-
def gen_subpaths_from_points_2d(
|
|
1348
|
+
def gen_subpaths_from_points_2d(
|
|
1349
|
+
self, points: CubicBezierPath
|
|
1350
|
+
) -> Iterable[CubicSpline]:
|
|
1082
1351
|
return self._gen_subpaths_from_points(
|
|
1083
1352
|
points,
|
|
1084
1353
|
lambda n: not self.consider_points_equals_2d(points[n - 1], points[n]),
|
|
1085
1354
|
)
|
|
1086
1355
|
|
|
1087
|
-
def get_subpaths(self) ->
|
|
1356
|
+
def get_subpaths(self) -> list[CubicSpline]:
|
|
1088
1357
|
"""Returns subpaths formed by the curves of the VMobject.
|
|
1089
1358
|
|
|
1090
1359
|
Subpaths are ranges of curves with each pair of consecutive curves having their end/start points coincident.
|
|
1091
1360
|
|
|
1092
1361
|
Returns
|
|
1093
1362
|
-------
|
|
1094
|
-
|
|
1363
|
+
list[CubicSpline]
|
|
1095
1364
|
subpaths.
|
|
1096
1365
|
"""
|
|
1097
1366
|
return self.get_subpaths_from_points(self.points)
|
|
1098
1367
|
|
|
1099
|
-
def get_nth_curve_points(self, n: int) ->
|
|
1368
|
+
def get_nth_curve_points(self, n: int) -> CubicBezierPoints:
|
|
1100
1369
|
"""Returns the points defining the nth curve of the vmobject.
|
|
1101
1370
|
|
|
1102
1371
|
Parameters
|
|
@@ -1106,14 +1375,14 @@ class VMobject(Mobject):
|
|
|
1106
1375
|
|
|
1107
1376
|
Returns
|
|
1108
1377
|
-------
|
|
1109
|
-
|
|
1110
|
-
points
|
|
1378
|
+
CubicBezierPoints
|
|
1379
|
+
points defining the nth bezier curve (anchors, handles)
|
|
1111
1380
|
"""
|
|
1112
1381
|
assert n < self.get_num_curves()
|
|
1113
1382
|
nppcc = self.n_points_per_cubic_curve
|
|
1114
1383
|
return self.points[nppcc * n : nppcc * (n + 1)]
|
|
1115
1384
|
|
|
1116
|
-
def get_nth_curve_function(self, n: int) ->
|
|
1385
|
+
def get_nth_curve_function(self, n: int) -> Callable[[float], Point3D]:
|
|
1117
1386
|
"""Returns the expression of the nth curve.
|
|
1118
1387
|
|
|
1119
1388
|
Parameters
|
|
@@ -1123,7 +1392,7 @@ class VMobject(Mobject):
|
|
|
1123
1392
|
|
|
1124
1393
|
Returns
|
|
1125
1394
|
-------
|
|
1126
|
-
|
|
1395
|
+
Callable[float, Point3D]
|
|
1127
1396
|
expression of the nth bezier curve.
|
|
1128
1397
|
"""
|
|
1129
1398
|
return bezier(self.get_nth_curve_points(n))
|
|
@@ -1132,7 +1401,7 @@ class VMobject(Mobject):
|
|
|
1132
1401
|
self,
|
|
1133
1402
|
n: int,
|
|
1134
1403
|
sample_points: int | None = None,
|
|
1135
|
-
) ->
|
|
1404
|
+
) -> npt.NDArray[ManimFloat]:
|
|
1136
1405
|
"""Returns the array of short line lengths used for length approximation.
|
|
1137
1406
|
|
|
1138
1407
|
Parameters
|
|
@@ -1144,7 +1413,6 @@ class VMobject(Mobject):
|
|
|
1144
1413
|
|
|
1145
1414
|
Returns
|
|
1146
1415
|
-------
|
|
1147
|
-
np.ndarray
|
|
1148
1416
|
The short length-pieces of the nth curve.
|
|
1149
1417
|
"""
|
|
1150
1418
|
if sample_points is None:
|
|
@@ -1176,7 +1444,6 @@ class VMobject(Mobject):
|
|
|
1176
1444
|
length : :class:`float`
|
|
1177
1445
|
The length of the nth curve.
|
|
1178
1446
|
"""
|
|
1179
|
-
|
|
1180
1447
|
_, length = self.get_nth_curve_function_with_length(n, sample_points)
|
|
1181
1448
|
|
|
1182
1449
|
return length
|
|
@@ -1185,7 +1452,7 @@ class VMobject(Mobject):
|
|
|
1185
1452
|
self,
|
|
1186
1453
|
n: int,
|
|
1187
1454
|
sample_points: int | None = None,
|
|
1188
|
-
) -> tuple[
|
|
1455
|
+
) -> tuple[Callable[[float], Point3D], float]:
|
|
1189
1456
|
"""Returns the expression of the nth curve along with its (approximate) length.
|
|
1190
1457
|
|
|
1191
1458
|
Parameters
|
|
@@ -1197,12 +1464,11 @@ class VMobject(Mobject):
|
|
|
1197
1464
|
|
|
1198
1465
|
Returns
|
|
1199
1466
|
-------
|
|
1200
|
-
curve :
|
|
1467
|
+
curve : Callable[[float], Point3D]
|
|
1201
1468
|
The function for the nth curve.
|
|
1202
1469
|
length : :class:`float`
|
|
1203
1470
|
The length of the nth curve.
|
|
1204
1471
|
"""
|
|
1205
|
-
|
|
1206
1472
|
curve = self.get_nth_curve_function(n)
|
|
1207
1473
|
norms = self.get_nth_curve_length_pieces(n, sample_points=sample_points)
|
|
1208
1474
|
length = np.sum(norms)
|
|
@@ -1215,22 +1481,21 @@ class VMobject(Mobject):
|
|
|
1215
1481
|
Returns
|
|
1216
1482
|
-------
|
|
1217
1483
|
int
|
|
1218
|
-
number of curves
|
|
1484
|
+
number of curves of the vmobject.
|
|
1219
1485
|
"""
|
|
1220
1486
|
nppcc = self.n_points_per_cubic_curve
|
|
1221
1487
|
return len(self.points) // nppcc
|
|
1222
1488
|
|
|
1223
1489
|
def get_curve_functions(
|
|
1224
1490
|
self,
|
|
1225
|
-
) ->
|
|
1491
|
+
) -> Iterable[Callable[[float], Point3D]]:
|
|
1226
1492
|
"""Gets the functions for the curves of the mobject.
|
|
1227
1493
|
|
|
1228
1494
|
Returns
|
|
1229
1495
|
-------
|
|
1230
|
-
|
|
1496
|
+
Iterable[Callable[[float], Point3D]]
|
|
1231
1497
|
The functions for the curves.
|
|
1232
1498
|
"""
|
|
1233
|
-
|
|
1234
1499
|
num_curves = self.get_num_curves()
|
|
1235
1500
|
|
|
1236
1501
|
for n in range(num_curves):
|
|
@@ -1238,7 +1503,7 @@ class VMobject(Mobject):
|
|
|
1238
1503
|
|
|
1239
1504
|
def get_curve_functions_with_lengths(
|
|
1240
1505
|
self, **kwargs
|
|
1241
|
-
) ->
|
|
1506
|
+
) -> Iterable[tuple[Callable[[float], Point3D], float]]:
|
|
1242
1507
|
"""Gets the functions and lengths of the curves for the mobject.
|
|
1243
1508
|
|
|
1244
1509
|
Parameters
|
|
@@ -1248,16 +1513,15 @@ class VMobject(Mobject):
|
|
|
1248
1513
|
|
|
1249
1514
|
Returns
|
|
1250
1515
|
-------
|
|
1251
|
-
|
|
1516
|
+
Iterable[tuple[Callable[[float], Point3D], float]]
|
|
1252
1517
|
The functions and lengths of the curves.
|
|
1253
1518
|
"""
|
|
1254
|
-
|
|
1255
1519
|
num_curves = self.get_num_curves()
|
|
1256
1520
|
|
|
1257
1521
|
for n in range(num_curves):
|
|
1258
1522
|
yield self.get_nth_curve_function_with_length(n, **kwargs)
|
|
1259
1523
|
|
|
1260
|
-
def point_from_proportion(self, alpha: float) ->
|
|
1524
|
+
def point_from_proportion(self, alpha: float) -> Point3D:
|
|
1261
1525
|
"""Gets the point at a proportion along the path of the :class:`VMobject`.
|
|
1262
1526
|
|
|
1263
1527
|
Parameters
|
|
@@ -1276,8 +1540,23 @@ class VMobject(Mobject):
|
|
|
1276
1540
|
If ``alpha`` is not between 0 and 1.
|
|
1277
1541
|
:exc:`Exception`
|
|
1278
1542
|
If the :class:`VMobject` has no points.
|
|
1279
|
-
"""
|
|
1280
1543
|
|
|
1544
|
+
Example
|
|
1545
|
+
-------
|
|
1546
|
+
.. manim:: PointFromProportion
|
|
1547
|
+
:save_last_frame:
|
|
1548
|
+
|
|
1549
|
+
class PointFromProportion(Scene):
|
|
1550
|
+
def construct(self):
|
|
1551
|
+
line = Line(2*DL, 2*UR)
|
|
1552
|
+
self.add(line)
|
|
1553
|
+
colors = (RED, BLUE, YELLOW)
|
|
1554
|
+
proportions = (1/4, 1/2, 3/4)
|
|
1555
|
+
for color, proportion in zip(colors, proportions):
|
|
1556
|
+
self.add(Dot(color=color).move_to(
|
|
1557
|
+
line.point_from_proportion(proportion)
|
|
1558
|
+
))
|
|
1559
|
+
"""
|
|
1281
1560
|
if alpha < 0 or alpha > 1:
|
|
1282
1561
|
raise ValueError(f"Alpha {alpha} not between 0 and 1.")
|
|
1283
1562
|
|
|
@@ -1300,10 +1579,13 @@ class VMobject(Mobject):
|
|
|
1300
1579
|
return curve(residue)
|
|
1301
1580
|
|
|
1302
1581
|
current_length += length
|
|
1582
|
+
raise Exception(
|
|
1583
|
+
"Not sure how you reached here, please file a bug report at https://github.com/ManimCommunity/manim/issues/new/choose"
|
|
1584
|
+
)
|
|
1303
1585
|
|
|
1304
1586
|
def proportion_from_point(
|
|
1305
1587
|
self,
|
|
1306
|
-
point:
|
|
1588
|
+
point: Point3DLike,
|
|
1307
1589
|
) -> float:
|
|
1308
1590
|
"""Returns the proportion along the path of the :class:`VMobject`
|
|
1309
1591
|
a particular given point is at.
|
|
@@ -1357,7 +1639,7 @@ class VMobject(Mobject):
|
|
|
1357
1639
|
|
|
1358
1640
|
return alpha
|
|
1359
1641
|
|
|
1360
|
-
def get_anchors_and_handles(self) ->
|
|
1642
|
+
def get_anchors_and_handles(self) -> list[Point3D_Array]:
|
|
1361
1643
|
"""Returns anchors1, handles1, handles2, anchors2,
|
|
1362
1644
|
where (anchors1[i], handles1[i], handles2[i], anchors2[i])
|
|
1363
1645
|
will be four points defining a cubic bezier curve
|
|
@@ -1365,50 +1647,53 @@ class VMobject(Mobject):
|
|
|
1365
1647
|
|
|
1366
1648
|
Returns
|
|
1367
1649
|
-------
|
|
1368
|
-
|
|
1650
|
+
`list[Point3D_Array]`
|
|
1369
1651
|
Iterable of the anchors and handles.
|
|
1370
1652
|
"""
|
|
1371
1653
|
nppcc = self.n_points_per_cubic_curve
|
|
1372
1654
|
return [self.points[i::nppcc] for i in range(nppcc)]
|
|
1373
1655
|
|
|
1374
|
-
def get_start_anchors(self) ->
|
|
1656
|
+
def get_start_anchors(self) -> Point3D_Array:
|
|
1375
1657
|
"""Returns the start anchors of the bezier curves.
|
|
1376
1658
|
|
|
1377
1659
|
Returns
|
|
1378
1660
|
-------
|
|
1379
|
-
|
|
1661
|
+
Point3D_Array
|
|
1380
1662
|
Starting anchors
|
|
1381
1663
|
"""
|
|
1382
|
-
return self.points[
|
|
1664
|
+
return self.points[:: self.n_points_per_cubic_curve]
|
|
1383
1665
|
|
|
1384
|
-
def get_end_anchors(self) ->
|
|
1666
|
+
def get_end_anchors(self) -> Point3D_Array:
|
|
1385
1667
|
"""Return the end anchors of the bezier curves.
|
|
1386
1668
|
|
|
1387
1669
|
Returns
|
|
1388
1670
|
-------
|
|
1389
|
-
|
|
1671
|
+
Point3D_Array
|
|
1390
1672
|
Starting anchors
|
|
1391
1673
|
"""
|
|
1392
1674
|
nppcc = self.n_points_per_cubic_curve
|
|
1393
1675
|
return self.points[nppcc - 1 :: nppcc]
|
|
1394
1676
|
|
|
1395
|
-
def get_anchors(self) ->
|
|
1677
|
+
def get_anchors(self) -> list[Point3D]:
|
|
1396
1678
|
"""Returns the anchors of the curves forming the VMobject.
|
|
1397
1679
|
|
|
1398
1680
|
Returns
|
|
1399
1681
|
-------
|
|
1400
|
-
|
|
1682
|
+
Point3D_Array
|
|
1401
1683
|
The anchors.
|
|
1402
1684
|
"""
|
|
1403
1685
|
if self.points.shape[0] == 1:
|
|
1404
1686
|
return self.points
|
|
1405
|
-
return np.array(
|
|
1406
|
-
list(it.chain(*zip(self.get_start_anchors(), self.get_end_anchors()))),
|
|
1407
|
-
)
|
|
1408
1687
|
|
|
1409
|
-
|
|
1688
|
+
s = self.get_start_anchors()
|
|
1689
|
+
e = self.get_end_anchors()
|
|
1690
|
+
return list(it.chain.from_iterable(zip(s, e)))
|
|
1691
|
+
|
|
1692
|
+
def get_points_defining_boundary(self) -> Point3D_Array:
|
|
1410
1693
|
# Probably returns all anchors, but this is weird regarding the name of the method.
|
|
1411
|
-
return np.array(
|
|
1694
|
+
return np.array(
|
|
1695
|
+
tuple(it.chain(*(sm.get_anchors() for sm in self.get_family())))
|
|
1696
|
+
)
|
|
1412
1697
|
|
|
1413
1698
|
def get_arc_length(self, sample_points_per_curve: int | None = None) -> float:
|
|
1414
1699
|
"""Return the approximated length of the whole curve.
|
|
@@ -1423,7 +1708,6 @@ class VMobject(Mobject):
|
|
|
1423
1708
|
float
|
|
1424
1709
|
The length of the :class:`VMobject`.
|
|
1425
1710
|
"""
|
|
1426
|
-
|
|
1427
1711
|
return sum(
|
|
1428
1712
|
length
|
|
1429
1713
|
for _, length in self.get_curve_functions_with_lengths(
|
|
@@ -1432,7 +1716,7 @@ class VMobject(Mobject):
|
|
|
1432
1716
|
)
|
|
1433
1717
|
|
|
1434
1718
|
# Alignment
|
|
1435
|
-
def align_points(self, vmobject: VMobject):
|
|
1719
|
+
def align_points(self, vmobject: VMobject) -> Self:
|
|
1436
1720
|
"""Adds points to self and vmobject so that they both have the same number of subpaths, with
|
|
1437
1721
|
corresponding subpaths each containing the same number of points.
|
|
1438
1722
|
|
|
@@ -1448,6 +1732,10 @@ class VMobject(Mobject):
|
|
|
1448
1732
|
-------
|
|
1449
1733
|
:class:`VMobject`
|
|
1450
1734
|
``self``
|
|
1735
|
+
|
|
1736
|
+
See also
|
|
1737
|
+
--------
|
|
1738
|
+
:meth:`~.Mobject.interpolate`, :meth:`~.Mobject.align_data`
|
|
1451
1739
|
"""
|
|
1452
1740
|
self.align_rgbas(vmobject)
|
|
1453
1741
|
# TODO: This shortcut can be a bit over eager. What if they have the same length, but different subpath lengths?
|
|
@@ -1503,7 +1791,7 @@ class VMobject(Mobject):
|
|
|
1503
1791
|
vmobject.set_points(new_path2)
|
|
1504
1792
|
return self
|
|
1505
1793
|
|
|
1506
|
-
def insert_n_curves(self, n: int):
|
|
1794
|
+
def insert_n_curves(self, n: int) -> Self:
|
|
1507
1795
|
"""Inserts n curves to the bezier curves of the vmobject.
|
|
1508
1796
|
|
|
1509
1797
|
Parameters
|
|
@@ -1527,7 +1815,9 @@ class VMobject(Mobject):
|
|
|
1527
1815
|
self.append_points([new_path_point])
|
|
1528
1816
|
return self
|
|
1529
1817
|
|
|
1530
|
-
def insert_n_curves_to_point_list(
|
|
1818
|
+
def insert_n_curves_to_point_list(
|
|
1819
|
+
self, n: int, points: BezierPathLike
|
|
1820
|
+
) -> BezierPath:
|
|
1531
1821
|
"""Given an array of k points defining a bezier curves (anchors and handles), returns points defining exactly k + n bezier curves.
|
|
1532
1822
|
|
|
1533
1823
|
Parameters
|
|
@@ -1539,50 +1829,19 @@ class VMobject(Mobject):
|
|
|
1539
1829
|
|
|
1540
1830
|
Returns
|
|
1541
1831
|
-------
|
|
1542
|
-
np.ndarray
|
|
1543
1832
|
Points generated.
|
|
1544
1833
|
"""
|
|
1545
|
-
|
|
1546
1834
|
if len(points) == 1:
|
|
1547
1835
|
nppcc = self.n_points_per_cubic_curve
|
|
1548
1836
|
return np.repeat(points, nppcc * n, 0)
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
# it's total length is target_num. For example,
|
|
1555
|
-
# with curr_num = 10, target_num = 15, this would
|
|
1556
|
-
# be [0, 0, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9]
|
|
1557
|
-
repeat_indices = (np.arange(target_num, dtype="i") * curr_num) // target_num
|
|
1558
|
-
|
|
1559
|
-
# If the nth term of this list is k, it means
|
|
1560
|
-
# that the nth curve of our path should be split
|
|
1561
|
-
# into k pieces.
|
|
1562
|
-
# In the above example our array had the following elements
|
|
1563
|
-
# [0, 0, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9]
|
|
1564
|
-
# We have two 0s, one 1, two 2s and so on.
|
|
1565
|
-
# The split factors array would hence be:
|
|
1566
|
-
# [2, 1, 2, 1, 2, 1, 2, 1, 2, 1]
|
|
1567
|
-
split_factors = np.zeros(curr_num, dtype="i")
|
|
1568
|
-
for val in repeat_indices:
|
|
1569
|
-
split_factors[val] += 1
|
|
1570
|
-
|
|
1571
|
-
new_points = np.zeros((0, self.dim))
|
|
1572
|
-
for quad, sf in zip(bezier_quads, split_factors):
|
|
1573
|
-
# What was once a single cubic curve defined
|
|
1574
|
-
# by "quad" will now be broken into sf
|
|
1575
|
-
# smaller cubic curves
|
|
1576
|
-
alphas = np.linspace(0, 1, sf + 1)
|
|
1577
|
-
for a1, a2 in zip(alphas, alphas[1:]):
|
|
1578
|
-
new_points = np.append(
|
|
1579
|
-
new_points,
|
|
1580
|
-
partial_bezier_points(quad, a1, a2),
|
|
1581
|
-
axis=0,
|
|
1582
|
-
)
|
|
1837
|
+
bezier_tuples = self.get_cubic_bezier_tuples_from_points(points)
|
|
1838
|
+
current_number_of_curves = len(bezier_tuples)
|
|
1839
|
+
new_number_of_curves = current_number_of_curves + n
|
|
1840
|
+
new_bezier_tuples = bezier_remap(bezier_tuples, new_number_of_curves)
|
|
1841
|
+
new_points = new_bezier_tuples.reshape(-1, 3)
|
|
1583
1842
|
return new_points
|
|
1584
1843
|
|
|
1585
|
-
def align_rgbas(self, vmobject):
|
|
1844
|
+
def align_rgbas(self, vmobject: VMobject) -> Self:
|
|
1586
1845
|
attrs = ["fill_rgbas", "stroke_rgbas", "background_stroke_rgbas"]
|
|
1587
1846
|
for attr in attrs:
|
|
1588
1847
|
a1 = getattr(self, attr)
|
|
@@ -1595,14 +1854,16 @@ class VMobject(Mobject):
|
|
|
1595
1854
|
setattr(self, attr, new_a1)
|
|
1596
1855
|
return self
|
|
1597
1856
|
|
|
1598
|
-
def get_point_mobject(self, center=None):
|
|
1857
|
+
def get_point_mobject(self, center: Point3DLike | None = None) -> VectorizedPoint:
|
|
1599
1858
|
if center is None:
|
|
1600
1859
|
center = self.get_center()
|
|
1601
1860
|
point = VectorizedPoint(center)
|
|
1602
1861
|
point.match_style(self)
|
|
1603
1862
|
return point
|
|
1604
1863
|
|
|
1605
|
-
def interpolate_color(
|
|
1864
|
+
def interpolate_color(
|
|
1865
|
+
self, mobject1: VMobject, mobject2: VMobject, alpha: float
|
|
1866
|
+
) -> None:
|
|
1606
1867
|
attrs = [
|
|
1607
1868
|
"fill_rgbas",
|
|
1608
1869
|
"stroke_rgbas",
|
|
@@ -1619,71 +1880,110 @@ class VMobject(Mobject):
|
|
|
1619
1880
|
interpolate(getattr(mobject1, attr), getattr(mobject2, attr), alpha),
|
|
1620
1881
|
)
|
|
1621
1882
|
if alpha == 1.0:
|
|
1622
|
-
|
|
1883
|
+
val = getattr(mobject2, attr)
|
|
1884
|
+
if isinstance(val, np.ndarray):
|
|
1885
|
+
val = val.copy()
|
|
1886
|
+
setattr(self, attr, val)
|
|
1623
1887
|
|
|
1624
1888
|
def pointwise_become_partial(
|
|
1625
1889
|
self,
|
|
1626
1890
|
vmobject: VMobject,
|
|
1627
1891
|
a: float,
|
|
1628
1892
|
b: float,
|
|
1629
|
-
):
|
|
1630
|
-
"""Given
|
|
1631
|
-
|
|
1893
|
+
) -> Self:
|
|
1894
|
+
"""Given a 2nd :class:`.VMobject` ``vmobject``, a lower bound ``a`` and
|
|
1895
|
+
an upper bound ``b``, modify this :class:`.VMobject`'s points to
|
|
1896
|
+
match the portion of the Bézier spline described by ``vmobject.points``
|
|
1897
|
+
with the parameter ``t`` between ``a`` and ``b``.
|
|
1632
1898
|
|
|
1633
1899
|
Parameters
|
|
1634
1900
|
----------
|
|
1635
1901
|
vmobject
|
|
1636
|
-
The
|
|
1902
|
+
The :class:`.VMobject` that will serve as a model.
|
|
1637
1903
|
a
|
|
1638
|
-
|
|
1904
|
+
The lower bound for ``t``.
|
|
1639
1905
|
b
|
|
1640
|
-
|
|
1906
|
+
The upper bound for ``t``
|
|
1641
1907
|
|
|
1642
1908
|
Returns
|
|
1643
1909
|
-------
|
|
1644
|
-
:class
|
|
1645
|
-
|
|
1910
|
+
:class:`.VMobject`
|
|
1911
|
+
The :class:`.VMobject` itself, after the transformation.
|
|
1912
|
+
|
|
1913
|
+
Raises
|
|
1914
|
+
------
|
|
1915
|
+
TypeError
|
|
1916
|
+
If ``vmobject`` is not an instance of :class:`VMobject`.
|
|
1646
1917
|
"""
|
|
1647
|
-
|
|
1918
|
+
if not isinstance(vmobject, VMobject):
|
|
1919
|
+
raise TypeError(
|
|
1920
|
+
f"Expected a VMobject, got value {vmobject} of type "
|
|
1921
|
+
f"{type(vmobject).__name__}."
|
|
1922
|
+
)
|
|
1648
1923
|
# Partial curve includes three portions:
|
|
1649
|
-
# - A middle section, which matches the curve exactly
|
|
1650
|
-
# - A start, which is some ending portion of an inner cubic
|
|
1651
|
-
# - An end, which is the starting portion of a later inner cubic
|
|
1924
|
+
# - A middle section, which matches the curve exactly.
|
|
1925
|
+
# - A start, which is some ending portion of an inner cubic.
|
|
1926
|
+
# - An end, which is the starting portion of a later inner cubic.
|
|
1652
1927
|
if a <= 0 and b >= 1:
|
|
1653
1928
|
self.set_points(vmobject.points)
|
|
1654
1929
|
return self
|
|
1655
|
-
|
|
1656
|
-
|
|
1930
|
+
num_curves = vmobject.get_num_curves()
|
|
1931
|
+
if num_curves == 0:
|
|
1932
|
+
return self
|
|
1657
1933
|
|
|
1658
|
-
# The following two lines will compute which
|
|
1659
|
-
# The residue
|
|
1660
|
-
#
|
|
1661
|
-
|
|
1662
|
-
|
|
1934
|
+
# The following two lines will compute which Bézier curves of the given Mobject must be processed.
|
|
1935
|
+
# The residue indicates the proportion of the selected Bézier curve which must be selected.
|
|
1936
|
+
#
|
|
1937
|
+
# Example: if num_curves is 10, a is 0.34 and b is 0.78, then:
|
|
1938
|
+
# - lower_index is 3 and lower_residue is 0.4, which means the algorithm will look at the 3rd Bézier
|
|
1939
|
+
# and select its part which ranges from t=0.4 to t=1.
|
|
1940
|
+
# - upper_index is 7 and upper_residue is 0.8, which means the algorithm will look at the 7th Bézier
|
|
1941
|
+
# and select its part which ranges from t=0 to t=0.8.
|
|
1942
|
+
lower_index, lower_residue = integer_interpolate(0, num_curves, a)
|
|
1943
|
+
upper_index, upper_residue = integer_interpolate(0, num_curves, b)
|
|
1944
|
+
|
|
1945
|
+
nppc = self.n_points_per_curve
|
|
1946
|
+
|
|
1947
|
+
# Copy vmobject.points if vmobject is self to prevent unintended in-place modification
|
|
1948
|
+
vmobject_points = (
|
|
1949
|
+
vmobject.points.copy() if self is vmobject else vmobject.points
|
|
1950
|
+
)
|
|
1663
1951
|
|
|
1664
|
-
|
|
1665
|
-
if num_cubics == 0:
|
|
1666
|
-
return self
|
|
1952
|
+
# If both indices coincide, get a part of a single Bézier curve.
|
|
1667
1953
|
if lower_index == upper_index:
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1954
|
+
# Look at the "lower_index"-th Bézier curve and select its part from
|
|
1955
|
+
# t=lower_residue to t=upper_residue.
|
|
1956
|
+
self.points = partial_bezier_points(
|
|
1957
|
+
vmobject_points[nppc * lower_index : nppc * (lower_index + 1)],
|
|
1958
|
+
lower_residue,
|
|
1959
|
+
upper_residue,
|
|
1674
1960
|
)
|
|
1675
1961
|
else:
|
|
1676
|
-
|
|
1677
|
-
|
|
1962
|
+
# Allocate space for (upper_index-lower_index+1) Bézier curves.
|
|
1963
|
+
self.points = np.empty((nppc * (upper_index - lower_index + 1), self.dim))
|
|
1964
|
+
# Look at the "lower_index"-th Bezier curve and select its part from
|
|
1965
|
+
# t=lower_residue to t=1. This is the first curve in self.points.
|
|
1966
|
+
self.points[:nppc] = partial_bezier_points(
|
|
1967
|
+
vmobject_points[nppc * lower_index : nppc * (lower_index + 1)],
|
|
1968
|
+
lower_residue,
|
|
1969
|
+
1,
|
|
1678
1970
|
)
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
self.
|
|
1682
|
-
|
|
1971
|
+
# If there are more curves between the "lower_index"-th and the
|
|
1972
|
+
# "upper_index"-th Béziers, add them all to self.points.
|
|
1973
|
+
self.points[nppc:-nppc] = vmobject_points[
|
|
1974
|
+
nppc * (lower_index + 1) : nppc * upper_index
|
|
1975
|
+
]
|
|
1976
|
+
# Look at the "upper_index"-th Bézier curve and select its part from
|
|
1977
|
+
# t=0 to t=upper_residue. This is the last curve in self.points.
|
|
1978
|
+
self.points[-nppc:] = partial_bezier_points(
|
|
1979
|
+
vmobject_points[nppc * upper_index : nppc * (upper_index + 1)],
|
|
1980
|
+
0,
|
|
1981
|
+
upper_residue,
|
|
1683
1982
|
)
|
|
1983
|
+
|
|
1684
1984
|
return self
|
|
1685
1985
|
|
|
1686
|
-
def get_subcurve(self, a: float, b: float) ->
|
|
1986
|
+
def get_subcurve(self, a: float, b: float) -> Self:
|
|
1687
1987
|
"""Returns the subcurve of the VMobject between the interval [a, b].
|
|
1688
1988
|
The curve is a VMobject itself.
|
|
1689
1989
|
|
|
@@ -1711,7 +2011,7 @@ class VMobject(Mobject):
|
|
|
1711
2011
|
vmob.pointwise_become_partial(self, a, b)
|
|
1712
2012
|
return vmob
|
|
1713
2013
|
|
|
1714
|
-
def get_direction(self):
|
|
2014
|
+
def get_direction(self) -> Literal["CW", "CCW"]:
|
|
1715
2015
|
"""Uses :func:`~.space_ops.shoelace_direction` to calculate the direction.
|
|
1716
2016
|
The direction of points determines in which direction the
|
|
1717
2017
|
object is drawn, clockwise or counterclockwise.
|
|
@@ -1731,7 +2031,7 @@ class VMobject(Mobject):
|
|
|
1731
2031
|
"""
|
|
1732
2032
|
return shoelace_direction(self.get_start_anchors())
|
|
1733
2033
|
|
|
1734
|
-
def reverse_direction(self):
|
|
2034
|
+
def reverse_direction(self) -> Self:
|
|
1735
2035
|
"""Reverts the point direction by inverting the point order.
|
|
1736
2036
|
|
|
1737
2037
|
Returns
|
|
@@ -1756,7 +2056,7 @@ class VMobject(Mobject):
|
|
|
1756
2056
|
self.points = self.points[::-1]
|
|
1757
2057
|
return self
|
|
1758
2058
|
|
|
1759
|
-
def force_direction(self, target_direction:
|
|
2059
|
+
def force_direction(self, target_direction: Literal["CW", "CCW"]) -> Self:
|
|
1760
2060
|
"""Makes sure that points are either directed clockwise or
|
|
1761
2061
|
counterclockwise.
|
|
1762
2062
|
|
|
@@ -1799,19 +2099,21 @@ class VGroup(VMobject, metaclass=ConvertToOpenGL):
|
|
|
1799
2099
|
>>> triangle, square = Triangle(), Square()
|
|
1800
2100
|
>>> vg.add(triangle)
|
|
1801
2101
|
VGroup(Triangle)
|
|
1802
|
-
>>> vg + square
|
|
2102
|
+
>>> vg + square # a new VGroup is constructed
|
|
1803
2103
|
VGroup(Triangle, Square)
|
|
1804
|
-
>>> vg
|
|
2104
|
+
>>> vg # not modified
|
|
1805
2105
|
VGroup(Triangle)
|
|
1806
|
-
>>> vg += square
|
|
2106
|
+
>>> vg += square
|
|
2107
|
+
>>> vg # modifies vg
|
|
1807
2108
|
VGroup(Triangle, Square)
|
|
1808
2109
|
>>> vg.remove(triangle)
|
|
1809
2110
|
VGroup(Square)
|
|
1810
|
-
>>> vg - square
|
|
2111
|
+
>>> vg - square # a new VGroup is constructed
|
|
1811
2112
|
VGroup()
|
|
1812
|
-
>>> vg
|
|
2113
|
+
>>> vg # not modified
|
|
1813
2114
|
VGroup(Square)
|
|
1814
|
-
>>> vg -= square
|
|
2115
|
+
>>> vg -= square
|
|
2116
|
+
>>> vg # modifies vg
|
|
1815
2117
|
VGroup()
|
|
1816
2118
|
|
|
1817
2119
|
.. manim:: ArcShapeIris
|
|
@@ -1831,31 +2133,31 @@ class VGroup(VMobject, metaclass=ConvertToOpenGL):
|
|
|
1831
2133
|
|
|
1832
2134
|
"""
|
|
1833
2135
|
|
|
1834
|
-
def __init__(
|
|
2136
|
+
def __init__(
|
|
2137
|
+
self, *vmobjects: VMobject | Iterable[VMobject], **kwargs: Any
|
|
2138
|
+
) -> None:
|
|
1835
2139
|
super().__init__(**kwargs)
|
|
1836
2140
|
self.add(*vmobjects)
|
|
1837
2141
|
|
|
1838
|
-
def __repr__(self):
|
|
1839
|
-
return (
|
|
1840
|
-
self.__class__.__name__
|
|
1841
|
-
+ "("
|
|
1842
|
-
+ ", ".join(str(mob) for mob in self.submobjects)
|
|
1843
|
-
+ ")"
|
|
1844
|
-
)
|
|
2142
|
+
def __repr__(self) -> str:
|
|
2143
|
+
return f"{self.__class__.__name__}({', '.join(str(mob) for mob in self.submobjects)})"
|
|
1845
2144
|
|
|
1846
|
-
def __str__(self):
|
|
2145
|
+
def __str__(self) -> str:
|
|
1847
2146
|
return (
|
|
1848
2147
|
f"{self.__class__.__name__} of {len(self.submobjects)} "
|
|
1849
2148
|
f"submobject{'s' if len(self.submobjects) > 0 else ''}"
|
|
1850
2149
|
)
|
|
1851
2150
|
|
|
1852
|
-
def add(
|
|
1853
|
-
|
|
2151
|
+
def add(
|
|
2152
|
+
self,
|
|
2153
|
+
*vmobjects: VMobject | Iterable[VMobject],
|
|
2154
|
+
) -> Self:
|
|
2155
|
+
"""Checks if all passed elements are an instance, or iterables of VMobject and then adds them to submobjects
|
|
1854
2156
|
|
|
1855
2157
|
Parameters
|
|
1856
2158
|
----------
|
|
1857
2159
|
vmobjects
|
|
1858
|
-
List of
|
|
2160
|
+
List or iterable of VMobjects to add
|
|
1859
2161
|
|
|
1860
2162
|
Returns
|
|
1861
2163
|
-------
|
|
@@ -1864,10 +2166,13 @@ class VGroup(VMobject, metaclass=ConvertToOpenGL):
|
|
|
1864
2166
|
Raises
|
|
1865
2167
|
------
|
|
1866
2168
|
TypeError
|
|
1867
|
-
If one element of the list is not an instance of VMobject
|
|
2169
|
+
If one element of the list, or iterable is not an instance of VMobject
|
|
1868
2170
|
|
|
1869
2171
|
Examples
|
|
1870
2172
|
--------
|
|
2173
|
+
The following example shows how to add individual or multiple `VMobject` instances through the `VGroup`
|
|
2174
|
+
constructor and its `.add()` method.
|
|
2175
|
+
|
|
1871
2176
|
.. manim:: AddToVGroup
|
|
1872
2177
|
|
|
1873
2178
|
class AddToVGroup(Scene):
|
|
@@ -1896,26 +2201,81 @@ class VGroup(VMobject, metaclass=ConvertToOpenGL):
|
|
|
1896
2201
|
self.play( # Animate group without component
|
|
1897
2202
|
(gr-circle_red).animate.shift(RIGHT)
|
|
1898
2203
|
)
|
|
2204
|
+
|
|
2205
|
+
A `VGroup` can be created using iterables as well. Keep in mind that all generated values from an
|
|
2206
|
+
iterable must be an instance of `VMobject`. This is demonstrated below:
|
|
2207
|
+
|
|
2208
|
+
.. manim:: AddIterableToVGroupExample
|
|
2209
|
+
:save_last_frame:
|
|
2210
|
+
|
|
2211
|
+
class AddIterableToVGroupExample(Scene):
|
|
2212
|
+
def construct(self):
|
|
2213
|
+
v = VGroup(
|
|
2214
|
+
Square(), # Singular VMobject instance
|
|
2215
|
+
[Circle(), Triangle()], # List of VMobject instances
|
|
2216
|
+
Dot(),
|
|
2217
|
+
(Dot() for _ in range(2)), # Iterable that generates VMobjects
|
|
2218
|
+
)
|
|
2219
|
+
v.arrange()
|
|
2220
|
+
self.add(v)
|
|
2221
|
+
|
|
2222
|
+
To facilitate this, the iterable is unpacked before its individual instances are added to the `VGroup`.
|
|
2223
|
+
As a result, when you index a `VGroup`, you will never get back an iterable.
|
|
2224
|
+
Instead, you will always receive `VMobject` instances, including those
|
|
2225
|
+
that were part of the iterable/s that you originally added to the `VGroup`.
|
|
1899
2226
|
"""
|
|
1900
|
-
if not all(isinstance(m, (VMobject, OpenGLVMobject)) for m in vmobjects):
|
|
1901
|
-
raise TypeError("All submobjects must be of type VMobject")
|
|
1902
|
-
return super().add(*vmobjects)
|
|
1903
2227
|
|
|
1904
|
-
|
|
2228
|
+
def get_type_error_message(invalid_obj, invalid_indices):
|
|
2229
|
+
return (
|
|
2230
|
+
f"Only values of type {vmobject_render_type.__name__} can be added "
|
|
2231
|
+
"as submobjects of VGroup, but the value "
|
|
2232
|
+
f"{repr(invalid_obj)} (at index {invalid_indices[1]} of "
|
|
2233
|
+
f"parameter {invalid_indices[0]}) is of type "
|
|
2234
|
+
f"{type(invalid_obj).__name__}."
|
|
2235
|
+
)
|
|
2236
|
+
|
|
2237
|
+
vmobject_render_type = (
|
|
2238
|
+
OpenGLVMobject if config.renderer == RendererType.OPENGL else VMobject
|
|
2239
|
+
)
|
|
2240
|
+
valid_vmobjects = []
|
|
2241
|
+
|
|
2242
|
+
for i, vmobject in enumerate(vmobjects):
|
|
2243
|
+
if isinstance(vmobject, vmobject_render_type):
|
|
2244
|
+
valid_vmobjects.append(vmobject)
|
|
2245
|
+
elif isinstance(vmobject, Iterable) and not isinstance(
|
|
2246
|
+
vmobject, (Mobject, OpenGLMobject)
|
|
2247
|
+
):
|
|
2248
|
+
for j, subvmobject in enumerate(vmobject):
|
|
2249
|
+
if not isinstance(subvmobject, vmobject_render_type):
|
|
2250
|
+
raise TypeError(get_type_error_message(subvmobject, (i, j)))
|
|
2251
|
+
valid_vmobjects.append(subvmobject)
|
|
2252
|
+
elif isinstance(vmobject, Iterable) and isinstance(
|
|
2253
|
+
vmobject, (Mobject, OpenGLMobject)
|
|
2254
|
+
):
|
|
2255
|
+
raise TypeError(
|
|
2256
|
+
f"{get_type_error_message(vmobject, (i, 0))} "
|
|
2257
|
+
"You can try adding this value into a Group instead."
|
|
2258
|
+
)
|
|
2259
|
+
else:
|
|
2260
|
+
raise TypeError(get_type_error_message(vmobject, (i, 0)))
|
|
2261
|
+
|
|
2262
|
+
return super().add(*valid_vmobjects)
|
|
2263
|
+
|
|
2264
|
+
def __add__(self, vmobject: VMobject) -> Self:
|
|
1905
2265
|
return VGroup(*self.submobjects, vmobject)
|
|
1906
2266
|
|
|
1907
|
-
def __iadd__(self, vmobject):
|
|
2267
|
+
def __iadd__(self, vmobject: VMobject) -> Self:
|
|
1908
2268
|
return self.add(vmobject)
|
|
1909
2269
|
|
|
1910
|
-
def __sub__(self, vmobject):
|
|
2270
|
+
def __sub__(self, vmobject: VMobject) -> Self:
|
|
1911
2271
|
copy = VGroup(*self.submobjects)
|
|
1912
2272
|
copy.remove(vmobject)
|
|
1913
2273
|
return copy
|
|
1914
2274
|
|
|
1915
|
-
def __isub__(self, vmobject):
|
|
2275
|
+
def __isub__(self, vmobject: VMobject) -> Self:
|
|
1916
2276
|
return self.remove(vmobject)
|
|
1917
2277
|
|
|
1918
|
-
def __setitem__(self, key: int, value: VMobject |
|
|
2278
|
+
def __setitem__(self, key: int, value: VMobject | Sequence[VMobject]) -> None:
|
|
1919
2279
|
"""Override the [] operator for item assignment.
|
|
1920
2280
|
|
|
1921
2281
|
Parameters
|
|
@@ -1936,8 +2296,7 @@ class VGroup(VMobject, metaclass=ConvertToOpenGL):
|
|
|
1936
2296
|
>>> new_obj = VMobject()
|
|
1937
2297
|
>>> vgroup[0] = new_obj
|
|
1938
2298
|
"""
|
|
1939
|
-
|
|
1940
|
-
raise TypeError("All submobjects must be of type VMobject")
|
|
2299
|
+
self._assert_valid_submobjects(tuplify(value))
|
|
1941
2300
|
self.submobjects[key] = value
|
|
1942
2301
|
|
|
1943
2302
|
|
|
@@ -2042,27 +2401,25 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2042
2401
|
def __init__(
|
|
2043
2402
|
self,
|
|
2044
2403
|
mapping_or_iterable: (
|
|
2045
|
-
|
|
2046
|
-
| typing.Iterable[tuple[typing.Hashable, VMobject]]
|
|
2404
|
+
Mapping[Hashable, VMobject] | Iterable[tuple[Hashable, VMobject]]
|
|
2047
2405
|
) = {},
|
|
2048
2406
|
show_keys: bool = False,
|
|
2049
2407
|
**kwargs,
|
|
2050
|
-
):
|
|
2408
|
+
) -> None:
|
|
2051
2409
|
super().__init__(**kwargs)
|
|
2052
2410
|
self.show_keys = show_keys
|
|
2053
2411
|
self.submob_dict = {}
|
|
2054
2412
|
self.add(mapping_or_iterable)
|
|
2055
2413
|
|
|
2056
|
-
def __repr__(self):
|
|
2057
|
-
return __class__.__name__
|
|
2414
|
+
def __repr__(self) -> str:
|
|
2415
|
+
return f"{self.__class__.__name__}({repr(self.submob_dict)})"
|
|
2058
2416
|
|
|
2059
2417
|
def add(
|
|
2060
2418
|
self,
|
|
2061
2419
|
mapping_or_iterable: (
|
|
2062
|
-
|
|
2063
|
-
| typing.Iterable[tuple[typing.Hashable, VMobject]]
|
|
2420
|
+
Mapping[Hashable, VMobject] | Iterable[tuple[Hashable, VMobject]]
|
|
2064
2421
|
),
|
|
2065
|
-
):
|
|
2422
|
+
) -> Self:
|
|
2066
2423
|
"""Adds the key-value pairs to the :class:`VDict` object.
|
|
2067
2424
|
|
|
2068
2425
|
Also, it internally adds the value to the `submobjects` :class:`list`
|
|
@@ -2083,14 +2440,14 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2083
2440
|
Normal usage::
|
|
2084
2441
|
|
|
2085
2442
|
square_obj = Square()
|
|
2086
|
-
my_dict.add([(
|
|
2443
|
+
my_dict.add([("s", square_obj)])
|
|
2087
2444
|
"""
|
|
2088
2445
|
for key, value in dict(mapping_or_iterable).items():
|
|
2089
2446
|
self.add_key_value_pair(key, value)
|
|
2090
2447
|
|
|
2091
2448
|
return self
|
|
2092
2449
|
|
|
2093
|
-
def remove(self, key:
|
|
2450
|
+
def remove(self, key: Hashable) -> Self:
|
|
2094
2451
|
"""Removes the mobject from the :class:`VDict` object having the key `key`
|
|
2095
2452
|
|
|
2096
2453
|
Also, it internally removes the mobject from the `submobjects` :class:`list`
|
|
@@ -2110,15 +2467,15 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2110
2467
|
--------
|
|
2111
2468
|
Normal usage::
|
|
2112
2469
|
|
|
2113
|
-
my_dict.remove(
|
|
2470
|
+
my_dict.remove("square")
|
|
2114
2471
|
"""
|
|
2115
2472
|
if key not in self.submob_dict:
|
|
2116
|
-
raise KeyError("The given key '
|
|
2473
|
+
raise KeyError(f"The given key '{key!s}' is not present in the VDict")
|
|
2117
2474
|
super().remove(self.submob_dict[key])
|
|
2118
2475
|
del self.submob_dict[key]
|
|
2119
2476
|
return self
|
|
2120
2477
|
|
|
2121
|
-
def __getitem__(self, key:
|
|
2478
|
+
def __getitem__(self, key: Hashable):
|
|
2122
2479
|
"""Override the [] operator for item retrieval.
|
|
2123
2480
|
|
|
2124
2481
|
Parameters
|
|
@@ -2135,12 +2492,12 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2135
2492
|
--------
|
|
2136
2493
|
Normal usage::
|
|
2137
2494
|
|
|
2138
|
-
self.play(Create(my_dict[
|
|
2495
|
+
self.play(Create(my_dict["s"]))
|
|
2139
2496
|
"""
|
|
2140
2497
|
submob = self.submob_dict[key]
|
|
2141
2498
|
return submob
|
|
2142
2499
|
|
|
2143
|
-
def __setitem__(self, key:
|
|
2500
|
+
def __setitem__(self, key: Hashable, value: VMobject) -> None:
|
|
2144
2501
|
"""Override the [] operator for item assignment.
|
|
2145
2502
|
|
|
2146
2503
|
Parameters
|
|
@@ -2159,13 +2516,13 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2159
2516
|
Normal usage::
|
|
2160
2517
|
|
|
2161
2518
|
square_obj = Square()
|
|
2162
|
-
my_dict[
|
|
2519
|
+
my_dict["sq"] = square_obj
|
|
2163
2520
|
"""
|
|
2164
2521
|
if key in self.submob_dict:
|
|
2165
2522
|
self.remove(key)
|
|
2166
2523
|
self.add([(key, value)])
|
|
2167
2524
|
|
|
2168
|
-
def __delitem__(self, key:
|
|
2525
|
+
def __delitem__(self, key: Hashable):
|
|
2169
2526
|
"""Override the del operator for deleting an item.
|
|
2170
2527
|
|
|
2171
2528
|
Parameters
|
|
@@ -2197,7 +2554,7 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2197
2554
|
"""
|
|
2198
2555
|
del self.submob_dict[key]
|
|
2199
2556
|
|
|
2200
|
-
def __contains__(self, key:
|
|
2557
|
+
def __contains__(self, key: Hashable):
|
|
2201
2558
|
"""Override the in operator.
|
|
2202
2559
|
|
|
2203
2560
|
Parameters
|
|
@@ -2221,7 +2578,7 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2221
2578
|
"""
|
|
2222
2579
|
return key in self.submob_dict
|
|
2223
2580
|
|
|
2224
|
-
def get_all_submobjects(self):
|
|
2581
|
+
def get_all_submobjects(self) -> list[list]:
|
|
2225
2582
|
"""To get all the submobjects associated with a particular :class:`VDict` object
|
|
2226
2583
|
|
|
2227
2584
|
Returns
|
|
@@ -2239,7 +2596,7 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2239
2596
|
submobjects = self.submob_dict.values()
|
|
2240
2597
|
return submobjects
|
|
2241
2598
|
|
|
2242
|
-
def add_key_value_pair(self, key:
|
|
2599
|
+
def add_key_value_pair(self, key: Hashable, value: VMobject) -> None:
|
|
2243
2600
|
"""A utility function used by :meth:`add` to add the key-value pair
|
|
2244
2601
|
to :attr:`submob_dict`. Not really meant to be used externally.
|
|
2245
2602
|
|
|
@@ -2264,11 +2621,10 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2264
2621
|
Normal usage::
|
|
2265
2622
|
|
|
2266
2623
|
square_obj = Square()
|
|
2267
|
-
self.add_key_value_pair(
|
|
2624
|
+
self.add_key_value_pair("s", square_obj)
|
|
2268
2625
|
|
|
2269
2626
|
"""
|
|
2270
|
-
|
|
2271
|
-
raise TypeError("All submobjects must be of type VMobject")
|
|
2627
|
+
self._assert_valid_submobjects([value])
|
|
2272
2628
|
mob = value
|
|
2273
2629
|
if self.show_keys:
|
|
2274
2630
|
# This import is here and not at the top to avoid circular import
|
|
@@ -2284,14 +2640,14 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2284
2640
|
class VectorizedPoint(VMobject, metaclass=ConvertToOpenGL):
|
|
2285
2641
|
def __init__(
|
|
2286
2642
|
self,
|
|
2287
|
-
location=ORIGIN,
|
|
2288
|
-
color=BLACK,
|
|
2289
|
-
fill_opacity=0,
|
|
2290
|
-
stroke_width=0,
|
|
2291
|
-
artificial_width=0.01,
|
|
2292
|
-
artificial_height=0.01,
|
|
2643
|
+
location: Point3DLike = ORIGIN,
|
|
2644
|
+
color: ManimColor = BLACK,
|
|
2645
|
+
fill_opacity: float = 0,
|
|
2646
|
+
stroke_width: float = 0,
|
|
2647
|
+
artificial_width: float = 0.01,
|
|
2648
|
+
artificial_height: float = 0.01,
|
|
2293
2649
|
**kwargs,
|
|
2294
|
-
):
|
|
2650
|
+
) -> None:
|
|
2295
2651
|
self.artificial_width = artificial_width
|
|
2296
2652
|
self.artificial_height = artificial_height
|
|
2297
2653
|
super().__init__(
|
|
@@ -2305,17 +2661,17 @@ class VectorizedPoint(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2305
2661
|
basecls = OpenGLVMobject if config.renderer == RendererType.OPENGL else VMobject
|
|
2306
2662
|
|
|
2307
2663
|
@basecls.width.getter
|
|
2308
|
-
def width(self):
|
|
2664
|
+
def width(self) -> float:
|
|
2309
2665
|
return self.artificial_width
|
|
2310
2666
|
|
|
2311
2667
|
@basecls.height.getter
|
|
2312
|
-
def height(self):
|
|
2668
|
+
def height(self) -> float:
|
|
2313
2669
|
return self.artificial_height
|
|
2314
2670
|
|
|
2315
|
-
def get_location(self):
|
|
2671
|
+
def get_location(self) -> Point3D:
|
|
2316
2672
|
return np.array(self.points[0])
|
|
2317
2673
|
|
|
2318
|
-
def set_location(self, new_loc):
|
|
2674
|
+
def set_location(self, new_loc: Point3D):
|
|
2319
2675
|
self.set_points(np.array([new_loc]))
|
|
2320
2676
|
|
|
2321
2677
|
|
|
@@ -2336,7 +2692,7 @@ class CurvesAsSubmobjects(VGroup):
|
|
|
2336
2692
|
|
|
2337
2693
|
"""
|
|
2338
2694
|
|
|
2339
|
-
def __init__(self, vmobject, **kwargs):
|
|
2695
|
+
def __init__(self, vmobject: VMobject, **kwargs) -> None:
|
|
2340
2696
|
super().__init__(**kwargs)
|
|
2341
2697
|
tuples = vmobject.get_cubic_bezier_tuples()
|
|
2342
2698
|
for tup in tuples:
|
|
@@ -2345,6 +2701,69 @@ class CurvesAsSubmobjects(VGroup):
|
|
|
2345
2701
|
part.match_style(vmobject)
|
|
2346
2702
|
self.add(part)
|
|
2347
2703
|
|
|
2704
|
+
def point_from_proportion(self, alpha: float) -> Point3D:
|
|
2705
|
+
"""Gets the point at a proportion along the path of the :class:`CurvesAsSubmobjects`.
|
|
2706
|
+
|
|
2707
|
+
Parameters
|
|
2708
|
+
----------
|
|
2709
|
+
alpha
|
|
2710
|
+
The proportion along the the path of the :class:`CurvesAsSubmobjects`.
|
|
2711
|
+
|
|
2712
|
+
Returns
|
|
2713
|
+
-------
|
|
2714
|
+
:class:`numpy.ndarray`
|
|
2715
|
+
The point on the :class:`CurvesAsSubmobjects`.
|
|
2716
|
+
|
|
2717
|
+
Raises
|
|
2718
|
+
------
|
|
2719
|
+
:exc:`ValueError`
|
|
2720
|
+
If ``alpha`` is not between 0 and 1.
|
|
2721
|
+
:exc:`Exception`
|
|
2722
|
+
If the :class:`CurvesAsSubmobjects` has no submobjects, or no submobject has points.
|
|
2723
|
+
"""
|
|
2724
|
+
if alpha < 0 or alpha > 1:
|
|
2725
|
+
raise ValueError(f"Alpha {alpha} not between 0 and 1.")
|
|
2726
|
+
|
|
2727
|
+
self._throw_error_if_no_submobjects()
|
|
2728
|
+
submobjs_with_pts = self._get_submobjects_with_points()
|
|
2729
|
+
|
|
2730
|
+
if alpha == 1:
|
|
2731
|
+
return submobjs_with_pts[-1].points[-1]
|
|
2732
|
+
|
|
2733
|
+
submobjs_arc_lengths = tuple(
|
|
2734
|
+
part.get_arc_length() for part in submobjs_with_pts
|
|
2735
|
+
)
|
|
2736
|
+
|
|
2737
|
+
total_length = sum(submobjs_arc_lengths)
|
|
2738
|
+
target_length = alpha * total_length
|
|
2739
|
+
current_length = 0
|
|
2740
|
+
|
|
2741
|
+
for i, part in enumerate(submobjs_with_pts):
|
|
2742
|
+
part_length = submobjs_arc_lengths[i]
|
|
2743
|
+
if current_length + part_length >= target_length:
|
|
2744
|
+
residue = (target_length - current_length) / part_length
|
|
2745
|
+
return part.point_from_proportion(residue)
|
|
2746
|
+
|
|
2747
|
+
current_length += part_length
|
|
2748
|
+
|
|
2749
|
+
def _throw_error_if_no_submobjects(self):
|
|
2750
|
+
if len(self.submobjects) == 0:
|
|
2751
|
+
caller_name = sys._getframe(1).f_code.co_name
|
|
2752
|
+
raise Exception(
|
|
2753
|
+
f"Cannot call CurvesAsSubmobjects. {caller_name} for a CurvesAsSubmobject with no submobjects"
|
|
2754
|
+
)
|
|
2755
|
+
|
|
2756
|
+
def _get_submobjects_with_points(self):
|
|
2757
|
+
submobjs_with_pts = tuple(
|
|
2758
|
+
part for part in self.submobjects if len(part.points) > 0
|
|
2759
|
+
)
|
|
2760
|
+
if len(submobjs_with_pts) == 0:
|
|
2761
|
+
caller_name = sys._getframe(1).f_code.co_name
|
|
2762
|
+
raise Exception(
|
|
2763
|
+
f"Cannot call CurvesAsSubmobjects. {caller_name} for a CurvesAsSubmobject whose submobjects have no points"
|
|
2764
|
+
)
|
|
2765
|
+
return submobjs_with_pts
|
|
2766
|
+
|
|
2348
2767
|
|
|
2349
2768
|
class DashedVMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
2350
2769
|
"""A :class:`VMobject` composed of dashes instead of lines.
|
|
@@ -2402,18 +2821,32 @@ class DashedVMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2402
2821
|
|
|
2403
2822
|
def __init__(
|
|
2404
2823
|
self,
|
|
2405
|
-
vmobject,
|
|
2406
|
-
num_dashes=15,
|
|
2407
|
-
dashed_ratio=0.5,
|
|
2408
|
-
dash_offset=0,
|
|
2409
|
-
color=WHITE,
|
|
2410
|
-
equal_lengths=True,
|
|
2824
|
+
vmobject: VMobject,
|
|
2825
|
+
num_dashes: int = 15,
|
|
2826
|
+
dashed_ratio: float = 0.5,
|
|
2827
|
+
dash_offset: float = 0,
|
|
2828
|
+
color: ManimColor = WHITE,
|
|
2829
|
+
equal_lengths: bool = True,
|
|
2411
2830
|
**kwargs,
|
|
2412
|
-
):
|
|
2413
|
-
|
|
2831
|
+
) -> None:
|
|
2414
2832
|
self.dashed_ratio = dashed_ratio
|
|
2415
2833
|
self.num_dashes = num_dashes
|
|
2416
2834
|
super().__init__(color=color, **kwargs)
|
|
2835
|
+
|
|
2836
|
+
# Work on a copy to avoid mutating the caller's mobject (e.g. removing tips).
|
|
2837
|
+
base_vmobject = vmobject
|
|
2838
|
+
vmobject = base_vmobject.copy()
|
|
2839
|
+
|
|
2840
|
+
# TipableVMobject instances (Arrow, Vector, etc.) carry tips as submobjects.
|
|
2841
|
+
# When dashing such objects, each subcurve would otherwise include its own
|
|
2842
|
+
# tip, leading to many overlapping arrowheads. Pop tips from the working
|
|
2843
|
+
# copy and re-attach them only once after the dashes are created.
|
|
2844
|
+
tips = None
|
|
2845
|
+
if hasattr(vmobject, "pop_tips"):
|
|
2846
|
+
popped_tips = vmobject.pop_tips()
|
|
2847
|
+
if len(popped_tips.submobjects) > 0:
|
|
2848
|
+
tips = popped_tips
|
|
2849
|
+
|
|
2417
2850
|
r = self.dashed_ratio
|
|
2418
2851
|
n = self.num_dashes
|
|
2419
2852
|
if n > 0:
|
|
@@ -2422,15 +2855,12 @@ class DashedVMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2422
2855
|
if vmobject.is_closed():
|
|
2423
2856
|
void_len = (1 - r) / n
|
|
2424
2857
|
else:
|
|
2425
|
-
if n == 1
|
|
2426
|
-
void_len = 1 - r
|
|
2427
|
-
else:
|
|
2428
|
-
void_len = (1 - r) / (n - 1)
|
|
2858
|
+
void_len = 1 - r if n == 1 else (1 - r) / (n - 1)
|
|
2429
2859
|
|
|
2430
2860
|
period = dash_len + void_len
|
|
2431
2861
|
phase_shift = (dash_offset % 1) * period
|
|
2432
2862
|
|
|
2433
|
-
if vmobject.is_closed():
|
|
2863
|
+
if vmobject.is_closed(): # noqa: SIM108
|
|
2434
2864
|
# closed curves have equal amount of dashes and voids
|
|
2435
2865
|
pattern_len = 1
|
|
2436
2866
|
else:
|
|
@@ -2502,6 +2932,9 @@ class DashedVMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2502
2932
|
# Family is already taken care of by get_subcurve
|
|
2503
2933
|
# implementation
|
|
2504
2934
|
if config.renderer == RendererType.OPENGL:
|
|
2505
|
-
self.match_style(
|
|
2935
|
+
self.match_style(base_vmobject, recurse=False)
|
|
2506
2936
|
else:
|
|
2507
|
-
self.match_style(
|
|
2937
|
+
self.match_style(base_vmobject, family=False)
|
|
2938
|
+
|
|
2939
|
+
if tips is not None:
|
|
2940
|
+
self.add(*tips.submobjects)
|