manim 0.18.0.post0__py3-none-any.whl → 0.19.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of manim might be problematic. Click here for more details.
- manim/__init__.py +3 -6
- manim/__main__.py +61 -20
- manim/_config/__init__.py +6 -3
- manim/_config/cli_colors.py +16 -8
- manim/_config/default.cfg +1 -3
- manim/_config/logger_utils.py +14 -8
- manim/_config/utils.py +651 -472
- manim/animation/animation.py +152 -5
- manim/animation/composition.py +80 -39
- manim/animation/creation.py +196 -14
- manim/animation/fading.py +5 -9
- manim/animation/indication.py +103 -47
- manim/animation/movement.py +22 -5
- manim/animation/rotation.py +3 -2
- manim/animation/specialized.py +4 -6
- manim/animation/speedmodifier.py +10 -5
- manim/animation/transform.py +4 -5
- manim/animation/transform_matching_parts.py +1 -1
- manim/animation/updaters/mobject_update_utils.py +17 -14
- manim/camera/camera.py +15 -6
- manim/cli/__init__.py +17 -0
- manim/cli/cfg/group.py +70 -44
- manim/cli/checkhealth/checks.py +93 -75
- manim/cli/checkhealth/commands.py +14 -5
- manim/cli/default_group.py +157 -25
- manim/cli/init/commands.py +32 -24
- 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 +51 -15
- manim/cli/render/output_options.py +6 -5
- manim/cli/render/render_options.py +97 -32
- manim/constants.py +65 -19
- manim/gui/gui.py +2 -0
- manim/mobject/frame.py +0 -1
- manim/mobject/geometry/arc.py +112 -78
- manim/mobject/geometry/boolean_ops.py +32 -25
- manim/mobject/geometry/labeled.py +300 -77
- manim/mobject/geometry/line.py +132 -64
- manim/mobject/geometry/polygram.py +126 -30
- manim/mobject/geometry/shape_matchers.py +35 -15
- manim/mobject/geometry/tips.py +38 -29
- manim/mobject/graph.py +414 -133
- manim/mobject/graphing/coordinate_systems.py +126 -64
- manim/mobject/graphing/functions.py +25 -15
- manim/mobject/graphing/number_line.py +24 -10
- manim/mobject/graphing/probability.py +2 -10
- manim/mobject/graphing/scale.py +6 -5
- manim/mobject/matrix.py +17 -19
- manim/mobject/mobject.py +314 -165
- manim/mobject/opengl/opengl_compatibility.py +2 -0
- manim/mobject/opengl/opengl_geometry.py +30 -9
- manim/mobject/opengl/opengl_image_mobject.py +2 -0
- manim/mobject/opengl/opengl_mobject.py +509 -343
- manim/mobject/opengl/opengl_point_cloud_mobject.py +5 -7
- manim/mobject/opengl/opengl_surface.py +3 -2
- manim/mobject/opengl/opengl_three_dimensions.py +2 -0
- manim/mobject/opengl/opengl_vectorized_mobject.py +46 -79
- manim/mobject/svg/brace.py +63 -13
- manim/mobject/svg/svg_mobject.py +4 -3
- manim/mobject/table.py +11 -13
- manim/mobject/text/code_mobject.py +186 -548
- manim/mobject/text/numbers.py +9 -7
- manim/mobject/text/tex_mobject.py +23 -14
- manim/mobject/text/text_mobject.py +70 -24
- manim/mobject/three_d/polyhedra.py +98 -1
- manim/mobject/three_d/three_d_utils.py +4 -4
- manim/mobject/three_d/three_dimensions.py +62 -34
- manim/mobject/types/image_mobject.py +42 -24
- manim/mobject/types/point_cloud_mobject.py +105 -67
- manim/mobject/types/vectorized_mobject.py +496 -228
- manim/mobject/value_tracker.py +5 -4
- manim/mobject/vector_field.py +5 -5
- manim/opengl/__init__.py +3 -3
- manim/plugins/__init__.py +14 -1
- manim/plugins/plugins_flags.py +14 -8
- manim/renderer/cairo_renderer.py +20 -10
- manim/renderer/opengl_renderer.py +21 -23
- manim/renderer/opengl_renderer_window.py +2 -0
- manim/renderer/shader.py +2 -3
- manim/renderer/shader_wrapper.py +5 -2
- manim/renderer/vectorized_mobject_rendering.py +5 -0
- manim/scene/moving_camera_scene.py +23 -0
- manim/scene/scene.py +90 -43
- manim/scene/scene_file_writer.py +316 -165
- manim/scene/section.py +17 -15
- manim/scene/three_d_scene.py +13 -21
- manim/scene/vector_space_scene.py +22 -9
- manim/typing.py +830 -70
- manim/utils/bezier.py +1667 -399
- manim/utils/caching.py +13 -5
- manim/utils/color/AS2700.py +2 -0
- manim/utils/color/BS381.py +3 -0
- manim/utils/color/DVIPSNAMES.py +96 -0
- manim/utils/color/SVGNAMES.py +179 -0
- manim/utils/color/X11.py +3 -0
- manim/utils/color/XKCD.py +3 -0
- manim/utils/color/__init__.py +8 -5
- manim/utils/color/core.py +844 -309
- manim/utils/color/manim_colors.py +7 -9
- manim/utils/commands.py +48 -20
- manim/utils/config_ops.py +18 -13
- manim/utils/debug.py +8 -7
- manim/utils/deprecation.py +90 -40
- manim/utils/docbuild/__init__.py +17 -0
- manim/utils/docbuild/autoaliasattr_directive.py +234 -0
- manim/utils/docbuild/autocolor_directive.py +21 -17
- manim/utils/docbuild/manim_directive.py +50 -35
- 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 +26 -16
- manim/utils/hashing.py +9 -7
- manim/utils/images.py +10 -4
- manim/utils/ipython_magic.py +14 -8
- manim/utils/iterables.py +161 -119
- manim/utils/module_ops.py +57 -19
- manim/utils/opengl.py +83 -24
- manim/utils/parameter_parsing.py +32 -0
- manim/utils/paths.py +21 -23
- manim/utils/polylabel.py +168 -0
- manim/utils/qhull.py +218 -0
- manim/utils/rate_functions.py +74 -39
- manim/utils/simple_functions.py +24 -15
- manim/utils/sounds.py +7 -1
- manim/utils/space_ops.py +125 -69
- 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 +33 -18
- manim/utils/testing/frames_comparison.py +27 -19
- manim/utils/tex.py +127 -197
- manim/utils/tex_file_writing.py +47 -45
- manim/utils/tex_templates.py +2 -1
- manim/utils/unit.py +6 -5
- {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/LICENSE.community +1 -1
- {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/METADATA +40 -39
- manim-0.19.0.dist-info/RECORD +221 -0
- {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/WHEEL +1 -1
- manim/cli/new/__init__.py +0 -0
- manim/cli/new/group.py +0 -189
- manim/plugins/import_plugins.py +0 -43
- manim-0.18.0.post0.dist-info/RECORD +0 -217
- {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/LICENSE +0 -0
- {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/entry_points.txt +0 -0
|
@@ -14,55 +14,58 @@ __all__ = [
|
|
|
14
14
|
|
|
15
15
|
import itertools as it
|
|
16
16
|
import sys
|
|
17
|
-
from
|
|
18
|
-
|
|
19
|
-
Callable,
|
|
20
|
-
Generator,
|
|
21
|
-
Hashable,
|
|
22
|
-
Iterable,
|
|
23
|
-
Literal,
|
|
24
|
-
Mapping,
|
|
25
|
-
Sequence,
|
|
26
|
-
)
|
|
17
|
+
from collections.abc import Hashable, Iterable, Mapping, Sequence
|
|
18
|
+
from typing import TYPE_CHECKING, Callable, Literal
|
|
27
19
|
|
|
28
20
|
import numpy as np
|
|
29
|
-
import numpy.typing as npt
|
|
30
21
|
from PIL.Image import Image
|
|
31
|
-
from typing_extensions import Self
|
|
32
22
|
|
|
23
|
+
from manim import config
|
|
24
|
+
from manim.constants import *
|
|
25
|
+
from manim.mobject.mobject import Mobject
|
|
33
26
|
from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL
|
|
27
|
+
from manim.mobject.opengl.opengl_mobject import OpenGLMobject
|
|
34
28
|
from manim.mobject.opengl.opengl_vectorized_mobject import OpenGLVMobject
|
|
35
29
|
from manim.mobject.three_d.three_d_utils import (
|
|
36
30
|
get_3d_vmob_gradient_start_and_end_points,
|
|
37
31
|
)
|
|
38
|
-
|
|
39
|
-
from ... import config
|
|
40
|
-
from ...constants import *
|
|
41
|
-
from ...mobject.mobject import Mobject
|
|
42
|
-
from ...utils.bezier import (
|
|
32
|
+
from manim.utils.bezier import (
|
|
43
33
|
bezier,
|
|
44
|
-
|
|
34
|
+
bezier_remap,
|
|
35
|
+
get_smooth_cubic_bezier_handle_points,
|
|
45
36
|
integer_interpolate,
|
|
46
37
|
interpolate,
|
|
47
38
|
partial_bezier_points,
|
|
48
39
|
proportions_along_bezier_curve_for_point,
|
|
49
40
|
)
|
|
50
|
-
from
|
|
51
|
-
from
|
|
52
|
-
|
|
41
|
+
from manim.utils.color import BLACK, WHITE, ManimColor, ParsableManimColor
|
|
42
|
+
from manim.utils.iterables import (
|
|
43
|
+
make_even,
|
|
44
|
+
resize_array,
|
|
45
|
+
stretch_array_to_length,
|
|
46
|
+
tuplify,
|
|
47
|
+
)
|
|
48
|
+
from manim.utils.space_ops import rotate_vector, shoelace_direction
|
|
53
49
|
|
|
54
50
|
if TYPE_CHECKING:
|
|
51
|
+
from typing import Any
|
|
52
|
+
|
|
53
|
+
import numpy.typing as npt
|
|
54
|
+
from typing_extensions import Self
|
|
55
|
+
|
|
55
56
|
from manim.typing import (
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
CubicBezierPath,
|
|
58
|
+
CubicBezierPointsLike,
|
|
59
|
+
CubicSpline,
|
|
58
60
|
ManimFloat,
|
|
59
61
|
MappingFunction,
|
|
60
|
-
|
|
62
|
+
Point2DLike,
|
|
61
63
|
Point3D,
|
|
62
64
|
Point3D_Array,
|
|
63
|
-
|
|
65
|
+
Point3DLike,
|
|
66
|
+
Point3DLike_Array,
|
|
64
67
|
RGBA_Array_Float,
|
|
65
|
-
|
|
68
|
+
Vector3D,
|
|
66
69
|
Zeros,
|
|
67
70
|
)
|
|
68
71
|
|
|
@@ -114,7 +117,7 @@ class VMobject(Mobject):
|
|
|
114
117
|
background_stroke_width: float = 0,
|
|
115
118
|
sheen_factor: float = 0.0,
|
|
116
119
|
joint_type: LineJointType | None = None,
|
|
117
|
-
sheen_direction:
|
|
120
|
+
sheen_direction: Vector3D = UL,
|
|
118
121
|
close_new_points: bool = False,
|
|
119
122
|
pre_function_handle_to_anchor_scale_factor: float = 0.01,
|
|
120
123
|
make_smooth_after_applying_functions: bool = False,
|
|
@@ -123,7 +126,8 @@ class VMobject(Mobject):
|
|
|
123
126
|
# TODO, do we care about accounting for varying zoom levels?
|
|
124
127
|
tolerance_for_point_equality: float = 1e-6,
|
|
125
128
|
n_points_per_cubic_curve: int = 4,
|
|
126
|
-
|
|
129
|
+
cap_style: CapStyleType = CapStyleType.AUTO,
|
|
130
|
+
**kwargs: Any,
|
|
127
131
|
):
|
|
128
132
|
self.fill_opacity = fill_opacity
|
|
129
133
|
self.stroke_opacity = stroke_opacity
|
|
@@ -138,7 +142,7 @@ class VMobject(Mobject):
|
|
|
138
142
|
self.joint_type: LineJointType = (
|
|
139
143
|
LineJointType.AUTO if joint_type is None else joint_type
|
|
140
144
|
)
|
|
141
|
-
self.sheen_direction:
|
|
145
|
+
self.sheen_direction: Vector3D = sheen_direction
|
|
142
146
|
self.close_new_points: bool = close_new_points
|
|
143
147
|
self.pre_function_handle_to_anchor_scale_factor: float = (
|
|
144
148
|
pre_function_handle_to_anchor_scale_factor
|
|
@@ -150,6 +154,10 @@ class VMobject(Mobject):
|
|
|
150
154
|
self.shade_in_3d: bool = shade_in_3d
|
|
151
155
|
self.tolerance_for_point_equality: float = tolerance_for_point_equality
|
|
152
156
|
self.n_points_per_cubic_curve: int = n_points_per_cubic_curve
|
|
157
|
+
self._bezier_t_values: npt.NDArray[float] = np.linspace(
|
|
158
|
+
0, 1, n_points_per_cubic_curve
|
|
159
|
+
)
|
|
160
|
+
self.cap_style: CapStyleType = cap_style
|
|
153
161
|
super().__init__(**kwargs)
|
|
154
162
|
self.submobjects: list[VMobject]
|
|
155
163
|
|
|
@@ -162,6 +170,9 @@ class VMobject(Mobject):
|
|
|
162
170
|
if stroke_color is not None:
|
|
163
171
|
self.stroke_color = ManimColor.parse(stroke_color)
|
|
164
172
|
|
|
173
|
+
def _assert_valid_submobjects(self, submobjects: Iterable[VMobject]) -> Self:
|
|
174
|
+
return self._assert_valid_submobjects_internal(submobjects, VMobject)
|
|
175
|
+
|
|
165
176
|
# OpenGL compatibility
|
|
166
177
|
@property
|
|
167
178
|
def n_points_per_curve(self) -> int:
|
|
@@ -335,11 +346,39 @@ class VMobject(Mobject):
|
|
|
335
346
|
setattr(self, opacity_name, opacity)
|
|
336
347
|
if color is not None and background:
|
|
337
348
|
if isinstance(color, (list, tuple)):
|
|
338
|
-
self.background_stroke_color = color
|
|
349
|
+
self.background_stroke_color = ManimColor.parse(color)
|
|
339
350
|
else:
|
|
340
351
|
self.background_stroke_color = ManimColor(color)
|
|
341
352
|
return self
|
|
342
353
|
|
|
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
|
+
|
|
343
382
|
def set_background_stroke(self, **kwargs) -> Self:
|
|
344
383
|
kwargs["background"] = True
|
|
345
384
|
self.set_stroke(**kwargs)
|
|
@@ -356,7 +395,7 @@ class VMobject(Mobject):
|
|
|
356
395
|
background_stroke_width: float | None = None,
|
|
357
396
|
background_stroke_opacity: float | None = None,
|
|
358
397
|
sheen_factor: float | None = None,
|
|
359
|
-
sheen_direction:
|
|
398
|
+
sheen_direction: Vector3D | None = None,
|
|
360
399
|
background_image: Image | str | None = None,
|
|
361
400
|
family: bool = True,
|
|
362
401
|
) -> Self:
|
|
@@ -433,6 +472,64 @@ class VMobject(Mobject):
|
|
|
433
472
|
self.set_stroke(opacity=opacity, family=family, background=True)
|
|
434
473
|
return self
|
|
435
474
|
|
|
475
|
+
def scale(self, scale_factor: float, scale_stroke: bool = False, **kwargs) -> Self:
|
|
476
|
+
r"""Scale the size by a factor.
|
|
477
|
+
|
|
478
|
+
Default behavior is to scale about the center of the vmobject.
|
|
479
|
+
|
|
480
|
+
Parameters
|
|
481
|
+
----------
|
|
482
|
+
scale_factor
|
|
483
|
+
The scaling factor :math:`\alpha`. If :math:`0 < |\alpha| < 1`, the mobject
|
|
484
|
+
will shrink, and for :math:`|\alpha| > 1` it will grow. Furthermore,
|
|
485
|
+
if :math:`\alpha < 0`, the mobject is also flipped.
|
|
486
|
+
scale_stroke
|
|
487
|
+
Boolean determining if the object's outline is scaled when the object is scaled.
|
|
488
|
+
If enabled, and object with 2px outline is scaled by a factor of .5, it will have an outline of 1px.
|
|
489
|
+
kwargs
|
|
490
|
+
Additional keyword arguments passed to
|
|
491
|
+
:meth:`~.Mobject.scale`.
|
|
492
|
+
|
|
493
|
+
Returns
|
|
494
|
+
-------
|
|
495
|
+
:class:`VMobject`
|
|
496
|
+
``self``
|
|
497
|
+
|
|
498
|
+
Examples
|
|
499
|
+
--------
|
|
500
|
+
|
|
501
|
+
.. manim:: MobjectScaleExample
|
|
502
|
+
:save_last_frame:
|
|
503
|
+
|
|
504
|
+
class MobjectScaleExample(Scene):
|
|
505
|
+
def construct(self):
|
|
506
|
+
c1 = Circle(1, RED).set_x(-1)
|
|
507
|
+
c2 = Circle(1, GREEN).set_x(1)
|
|
508
|
+
|
|
509
|
+
vg = VGroup(c1, c2)
|
|
510
|
+
vg.set_stroke(width=50)
|
|
511
|
+
self.add(vg)
|
|
512
|
+
|
|
513
|
+
self.play(
|
|
514
|
+
c1.animate.scale(.25),
|
|
515
|
+
c2.animate.scale(.25,
|
|
516
|
+
scale_stroke=True)
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
See also
|
|
520
|
+
--------
|
|
521
|
+
:meth:`move_to`
|
|
522
|
+
|
|
523
|
+
"""
|
|
524
|
+
if scale_stroke:
|
|
525
|
+
self.set_stroke(width=abs(scale_factor) * self.get_stroke_width())
|
|
526
|
+
self.set_stroke(
|
|
527
|
+
width=abs(scale_factor) * self.get_stroke_width(background=True),
|
|
528
|
+
background=True,
|
|
529
|
+
)
|
|
530
|
+
super().scale(scale_factor, **kwargs)
|
|
531
|
+
return self
|
|
532
|
+
|
|
436
533
|
def fade(self, darkness: float = 0.5, family: bool = True) -> Self:
|
|
437
534
|
factor = 1.0 - darkness
|
|
438
535
|
self.set_fill(opacity=factor * self.get_fill_opacity(), family=False)
|
|
@@ -523,7 +620,7 @@ class VMobject(Mobject):
|
|
|
523
620
|
|
|
524
621
|
color = property(get_color, set_color)
|
|
525
622
|
|
|
526
|
-
def set_sheen_direction(self, direction:
|
|
623
|
+
def set_sheen_direction(self, direction: Vector3D, family: bool = True) -> Self:
|
|
527
624
|
"""Sets the direction of the applied sheen.
|
|
528
625
|
|
|
529
626
|
Parameters
|
|
@@ -542,17 +639,16 @@ class VMobject(Mobject):
|
|
|
542
639
|
:meth:`~.VMobject.set_sheen`
|
|
543
640
|
:meth:`~.VMobject.rotate_sheen_direction`
|
|
544
641
|
"""
|
|
545
|
-
|
|
546
642
|
direction = np.array(direction)
|
|
547
643
|
if family:
|
|
548
644
|
for submob in self.get_family():
|
|
549
645
|
submob.sheen_direction = direction
|
|
550
646
|
else:
|
|
551
|
-
self.sheen_direction:
|
|
647
|
+
self.sheen_direction: Vector3D = direction
|
|
552
648
|
return self
|
|
553
649
|
|
|
554
650
|
def rotate_sheen_direction(
|
|
555
|
-
self, angle: float, axis:
|
|
651
|
+
self, angle: float, axis: Vector3D = OUT, family: bool = True
|
|
556
652
|
) -> Self:
|
|
557
653
|
"""Rotates the direction of the applied sheen.
|
|
558
654
|
|
|
@@ -585,7 +681,7 @@ class VMobject(Mobject):
|
|
|
585
681
|
return self
|
|
586
682
|
|
|
587
683
|
def set_sheen(
|
|
588
|
-
self, factor: float, direction:
|
|
684
|
+
self, factor: float, direction: Vector3D | None = None, family: bool = True
|
|
589
685
|
) -> Self:
|
|
590
686
|
"""Applies a color gradient from a direction.
|
|
591
687
|
|
|
@@ -608,7 +704,6 @@ class VMobject(Mobject):
|
|
|
608
704
|
circle = Circle(fill_opacity=1).set_sheen(-0.3, DR)
|
|
609
705
|
self.add(circle)
|
|
610
706
|
"""
|
|
611
|
-
|
|
612
707
|
if family:
|
|
613
708
|
for submob in self.submobjects:
|
|
614
709
|
submob.set_sheen(factor, direction, family)
|
|
@@ -623,7 +718,7 @@ class VMobject(Mobject):
|
|
|
623
718
|
self.set_fill(self.get_fill_color(), family=family)
|
|
624
719
|
return self
|
|
625
720
|
|
|
626
|
-
def get_sheen_direction(self) ->
|
|
721
|
+
def get_sheen_direction(self) -> Vector3D:
|
|
627
722
|
return np.array(self.sheen_direction)
|
|
628
723
|
|
|
629
724
|
def get_sheen_factor(self) -> float:
|
|
@@ -664,14 +759,14 @@ class VMobject(Mobject):
|
|
|
664
759
|
submob.z_index_group = self
|
|
665
760
|
return self
|
|
666
761
|
|
|
667
|
-
def set_points(self, points:
|
|
762
|
+
def set_points(self, points: Point3DLike_Array) -> Self:
|
|
668
763
|
self.points: Point3D_Array = np.array(points)
|
|
669
764
|
return self
|
|
670
765
|
|
|
671
766
|
def resize_points(
|
|
672
767
|
self,
|
|
673
768
|
new_length: int,
|
|
674
|
-
resize_func: Callable[[
|
|
769
|
+
resize_func: Callable[[Point3D_Array, int], Point3D_Array] = resize_array,
|
|
675
770
|
) -> Self:
|
|
676
771
|
"""Resize the array of anchor points and handles to have
|
|
677
772
|
the specified size.
|
|
@@ -691,10 +786,10 @@ class VMobject(Mobject):
|
|
|
691
786
|
|
|
692
787
|
def set_anchors_and_handles(
|
|
693
788
|
self,
|
|
694
|
-
anchors1:
|
|
695
|
-
handles1:
|
|
696
|
-
handles2:
|
|
697
|
-
anchors2:
|
|
789
|
+
anchors1: Point3DLike_Array,
|
|
790
|
+
handles1: Point3DLike_Array,
|
|
791
|
+
handles2: Point3DLike_Array,
|
|
792
|
+
anchors2: Point3DLike_Array,
|
|
698
793
|
) -> Self:
|
|
699
794
|
"""Given two sets of anchors and handles, process them to set them as anchors
|
|
700
795
|
and handles of the VMobject.
|
|
@@ -712,7 +807,7 @@ class VMobject(Mobject):
|
|
|
712
807
|
assert len(anchors1) == len(handles1) == len(handles2) == len(anchors2)
|
|
713
808
|
nppcc = self.n_points_per_cubic_curve # 4
|
|
714
809
|
total_len = nppcc * len(anchors1)
|
|
715
|
-
self.points = np.
|
|
810
|
+
self.points = np.empty((total_len, self.dim))
|
|
716
811
|
# the following will, from the four sets, dispatch them in points such that
|
|
717
812
|
# self.points = [
|
|
718
813
|
# anchors1[0], handles1[0], handles2[0], anchors1[0], anchors1[1],
|
|
@@ -724,31 +819,69 @@ class VMobject(Mobject):
|
|
|
724
819
|
return self
|
|
725
820
|
|
|
726
821
|
def clear_points(self) -> None:
|
|
822
|
+
# TODO: shouldn't this return self instead of None?
|
|
727
823
|
self.points = np.zeros((0, self.dim))
|
|
728
824
|
|
|
729
|
-
def append_points(self, new_points:
|
|
825
|
+
def append_points(self, new_points: Point3DLike_Array) -> Self:
|
|
826
|
+
"""Append the given ``new_points`` to the end of
|
|
827
|
+
:attr:`VMobject.points`.
|
|
828
|
+
|
|
829
|
+
Parameters
|
|
830
|
+
----------
|
|
831
|
+
new_points
|
|
832
|
+
An array of 3D points to append.
|
|
833
|
+
|
|
834
|
+
Returns
|
|
835
|
+
-------
|
|
836
|
+
:class:`VMobject`
|
|
837
|
+
The VMobject itself, after appending ``new_points``.
|
|
838
|
+
"""
|
|
730
839
|
# TODO, check that number new points is a multiple of 4?
|
|
731
840
|
# or else that if len(self.points) % 4 == 1, then
|
|
732
841
|
# len(new_points) % 4 == 3?
|
|
733
|
-
|
|
842
|
+
n = len(self.points)
|
|
843
|
+
points = np.empty((n + len(new_points), self.dim))
|
|
844
|
+
points[:n] = self.points
|
|
845
|
+
points[n:] = new_points
|
|
846
|
+
self.points = points
|
|
734
847
|
return self
|
|
735
848
|
|
|
736
|
-
def start_new_path(self, point:
|
|
737
|
-
|
|
849
|
+
def start_new_path(self, point: Point3DLike) -> Self:
|
|
850
|
+
"""Append a ``point`` to the :attr:`VMobject.points`, which will be the
|
|
851
|
+
beginning of a new Bézier curve in the path given by the points. If
|
|
852
|
+
there's an unfinished curve at the end of :attr:`VMobject.points`,
|
|
853
|
+
complete it by appending the last Bézier curve's start anchor as many
|
|
854
|
+
times as needed.
|
|
855
|
+
|
|
856
|
+
Parameters
|
|
857
|
+
----------
|
|
858
|
+
point
|
|
859
|
+
A 3D point to append to :attr:`VMobject.points`.
|
|
860
|
+
|
|
861
|
+
Returns
|
|
862
|
+
-------
|
|
863
|
+
:class:`VMobject`
|
|
864
|
+
The VMobject itself, after appending ``point`` and starting a new
|
|
865
|
+
curve.
|
|
866
|
+
"""
|
|
867
|
+
n_points = len(self.points)
|
|
868
|
+
nppc = self.n_points_per_curve
|
|
869
|
+
if n_points % nppc != 0:
|
|
738
870
|
# close the open path by appending the last
|
|
739
871
|
# start anchor sufficiently often
|
|
740
872
|
last_anchor = self.get_start_anchors()[-1]
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
873
|
+
closure = [last_anchor] * (nppc - (n_points % nppc))
|
|
874
|
+
self.append_points(closure + [point])
|
|
875
|
+
else:
|
|
876
|
+
self.append_points([point])
|
|
744
877
|
return self
|
|
745
878
|
|
|
746
879
|
def add_cubic_bezier_curve(
|
|
747
880
|
self,
|
|
748
|
-
anchor1:
|
|
749
|
-
handle1:
|
|
750
|
-
handle2:
|
|
751
|
-
anchor2:
|
|
881
|
+
anchor1: Point3DLike,
|
|
882
|
+
handle1: Point3DLike,
|
|
883
|
+
handle2: Point3DLike,
|
|
884
|
+
anchor2: Point3DLike,
|
|
752
885
|
) -> None:
|
|
753
886
|
# TODO, check the len(self.points) % 4 == 0?
|
|
754
887
|
self.append_points([anchor1, handle1, handle2, anchor2])
|
|
@@ -759,9 +892,9 @@ class VMobject(Mobject):
|
|
|
759
892
|
|
|
760
893
|
def add_cubic_bezier_curve_to(
|
|
761
894
|
self,
|
|
762
|
-
handle1:
|
|
763
|
-
handle2:
|
|
764
|
-
anchor:
|
|
895
|
+
handle1: Point3DLike,
|
|
896
|
+
handle2: Point3DLike,
|
|
897
|
+
anchor: Point3DLike,
|
|
765
898
|
) -> Self:
|
|
766
899
|
"""Add cubic bezier curve to the path.
|
|
767
900
|
|
|
@@ -791,8 +924,8 @@ class VMobject(Mobject):
|
|
|
791
924
|
|
|
792
925
|
def add_quadratic_bezier_curve_to(
|
|
793
926
|
self,
|
|
794
|
-
handle:
|
|
795
|
-
anchor:
|
|
927
|
+
handle: Point3DLike,
|
|
928
|
+
anchor: Point3DLike,
|
|
796
929
|
) -> Self:
|
|
797
930
|
"""Add Quadratic bezier curve to the path.
|
|
798
931
|
|
|
@@ -815,30 +948,29 @@ class VMobject(Mobject):
|
|
|
815
948
|
)
|
|
816
949
|
return self
|
|
817
950
|
|
|
818
|
-
def add_line_to(self, point:
|
|
951
|
+
def add_line_to(self, point: Point3DLike) -> Self:
|
|
819
952
|
"""Add a straight line from the last point of VMobject to the given point.
|
|
820
953
|
|
|
821
954
|
Parameters
|
|
822
955
|
----------
|
|
823
956
|
|
|
824
957
|
point
|
|
825
|
-
end of the straight line.
|
|
958
|
+
The end of the straight line.
|
|
826
959
|
|
|
827
960
|
Returns
|
|
828
961
|
-------
|
|
829
962
|
:class:`VMobject`
|
|
830
963
|
``self``
|
|
831
964
|
"""
|
|
832
|
-
nppcc = self.n_points_per_cubic_curve
|
|
833
965
|
self.add_cubic_bezier_curve_to(
|
|
834
966
|
*(
|
|
835
|
-
interpolate(self.get_last_point(), point,
|
|
836
|
-
for
|
|
967
|
+
interpolate(self.get_last_point(), point, t)
|
|
968
|
+
for t in self._bezier_t_values[1:]
|
|
837
969
|
)
|
|
838
970
|
)
|
|
839
971
|
return self
|
|
840
972
|
|
|
841
|
-
def add_smooth_curve_to(self, *points:
|
|
973
|
+
def add_smooth_curve_to(self, *points: Point3DLike) -> Self:
|
|
842
974
|
"""Creates a smooth curve from given points and add it to the VMobject. If two points are passed in, the first is interpreted
|
|
843
975
|
as a handle, the second as an anchor.
|
|
844
976
|
|
|
@@ -897,16 +1029,58 @@ class VMobject(Mobject):
|
|
|
897
1029
|
if not self.is_closed():
|
|
898
1030
|
self.add_line_to(self.get_subpaths()[-1][0])
|
|
899
1031
|
|
|
900
|
-
def add_points_as_corners(self, points:
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
1032
|
+
def add_points_as_corners(self, points: Point3DLike_Array) -> Self:
|
|
1033
|
+
"""Append multiple straight lines at the end of
|
|
1034
|
+
:attr:`VMobject.points`, which connect the given ``points`` in order
|
|
1035
|
+
starting from the end of the current path. These ``points`` would be
|
|
1036
|
+
therefore the corners of the new polyline appended to the path.
|
|
1037
|
+
|
|
1038
|
+
Parameters
|
|
1039
|
+
----------
|
|
1040
|
+
points
|
|
1041
|
+
An array of 3D points representing the corners of the polyline to
|
|
1042
|
+
append to :attr:`VMobject.points`.
|
|
1043
|
+
|
|
1044
|
+
Returns
|
|
1045
|
+
-------
|
|
1046
|
+
:class:`VMobject`
|
|
1047
|
+
The VMobject itself, after appending the straight lines to its
|
|
1048
|
+
path.
|
|
1049
|
+
"""
|
|
1050
|
+
points = np.asarray(points).reshape(-1, self.dim)
|
|
1051
|
+
num_points = points.shape[0]
|
|
1052
|
+
if num_points == 0:
|
|
1053
|
+
return self
|
|
1054
|
+
|
|
1055
|
+
if self.has_new_path_started():
|
|
1056
|
+
# Pop the last point from self.points and
|
|
1057
|
+
# add it to start_corners
|
|
1058
|
+
start_corners = np.empty((num_points, self.dim))
|
|
1059
|
+
start_corners[0] = self.points[-1]
|
|
1060
|
+
start_corners[1:] = points[:-1]
|
|
1061
|
+
end_corners = points
|
|
1062
|
+
self.points = self.points[:-1]
|
|
1063
|
+
else:
|
|
1064
|
+
start_corners = points[:-1]
|
|
1065
|
+
end_corners = points[1:]
|
|
1066
|
+
|
|
1067
|
+
nppcc = self.n_points_per_cubic_curve
|
|
1068
|
+
new_points = np.empty((nppcc * start_corners.shape[0], self.dim))
|
|
1069
|
+
new_points[::nppcc] = start_corners
|
|
1070
|
+
new_points[nppcc - 1 :: nppcc] = end_corners
|
|
1071
|
+
for i, t in enumerate(self._bezier_t_values):
|
|
1072
|
+
new_points[i::nppcc] = interpolate(start_corners, end_corners, t)
|
|
904
1073
|
|
|
905
|
-
|
|
906
|
-
|
|
1074
|
+
self.append_points(new_points)
|
|
1075
|
+
return self
|
|
907
1076
|
|
|
908
|
-
|
|
909
|
-
|
|
1077
|
+
def set_points_as_corners(self, points: Point3DLike_Array) -> Self:
|
|
1078
|
+
"""Given an array of points, set them as corners of the
|
|
1079
|
+
:class:`VMobject`.
|
|
1080
|
+
|
|
1081
|
+
To achieve that, this algorithm sets handles aligned with the anchors
|
|
1082
|
+
such that the resultant Bézier curve will be the segment between the
|
|
1083
|
+
two anchors.
|
|
910
1084
|
|
|
911
1085
|
Parameters
|
|
912
1086
|
----------
|
|
@@ -916,18 +1090,38 @@ class VMobject(Mobject):
|
|
|
916
1090
|
Returns
|
|
917
1091
|
-------
|
|
918
1092
|
:class:`VMobject`
|
|
919
|
-
|
|
1093
|
+
The VMobject itself, after setting the new points as corners.
|
|
1094
|
+
|
|
1095
|
+
|
|
1096
|
+
Examples
|
|
1097
|
+
--------
|
|
1098
|
+
.. manim:: PointsAsCornersExample
|
|
1099
|
+
:save_last_frame:
|
|
1100
|
+
|
|
1101
|
+
class PointsAsCornersExample(Scene):
|
|
1102
|
+
def construct(self):
|
|
1103
|
+
corners = (
|
|
1104
|
+
# create square
|
|
1105
|
+
UR, UL,
|
|
1106
|
+
DL, DR,
|
|
1107
|
+
UR,
|
|
1108
|
+
# create crosses
|
|
1109
|
+
DL, UL,
|
|
1110
|
+
DR
|
|
1111
|
+
)
|
|
1112
|
+
vmob = VMobject(stroke_color=RED)
|
|
1113
|
+
vmob.set_points_as_corners(corners).scale(2)
|
|
1114
|
+
self.add(vmob)
|
|
920
1115
|
"""
|
|
921
|
-
nppcc = self.n_points_per_cubic_curve
|
|
922
1116
|
points = np.array(points)
|
|
923
1117
|
# This will set the handles aligned with the anchors.
|
|
924
1118
|
# Id est, a bezier curve will be the segment from the two anchors such that the handles belongs to this segment.
|
|
925
1119
|
self.set_anchors_and_handles(
|
|
926
|
-
*(interpolate(points[:-1], points[1:],
|
|
1120
|
+
*(interpolate(points[:-1], points[1:], t) for t in self._bezier_t_values)
|
|
927
1121
|
)
|
|
928
1122
|
return self
|
|
929
1123
|
|
|
930
|
-
def set_points_smoothly(self, points:
|
|
1124
|
+
def set_points_smoothly(self, points: Point3DLike_Array) -> Self:
|
|
931
1125
|
self.set_points_as_corners(points)
|
|
932
1126
|
self.make_smooth()
|
|
933
1127
|
return self
|
|
@@ -953,7 +1147,7 @@ class VMobject(Mobject):
|
|
|
953
1147
|
# The append is needed as the last element is not reached when slicing with numpy.
|
|
954
1148
|
anchors = np.append(subpath[::nppcc], subpath[-1:], 0)
|
|
955
1149
|
if mode == "smooth":
|
|
956
|
-
h1, h2 =
|
|
1150
|
+
h1, h2 = get_smooth_cubic_bezier_handle_points(anchors)
|
|
957
1151
|
else: # mode == "jagged"
|
|
958
1152
|
# The following will make the handles aligned with the anchors, thus making the bezier curve a segment
|
|
959
1153
|
a1 = anchors[:-1]
|
|
@@ -972,19 +1166,17 @@ class VMobject(Mobject):
|
|
|
972
1166
|
def make_jagged(self) -> Self:
|
|
973
1167
|
return self.change_anchor_mode("jagged")
|
|
974
1168
|
|
|
975
|
-
def add_subpath(self, points:
|
|
1169
|
+
def add_subpath(self, points: CubicBezierPathLike) -> Self:
|
|
976
1170
|
assert len(points) % 4 == 0
|
|
977
|
-
self.
|
|
1171
|
+
self.append_points(points)
|
|
978
1172
|
return self
|
|
979
1173
|
|
|
980
1174
|
def append_vectorized_mobject(self, vectorized_mobject: VMobject) -> None:
|
|
981
|
-
new_points = list(vectorized_mobject.points)
|
|
982
|
-
|
|
983
1175
|
if self.has_new_path_started():
|
|
984
1176
|
# Remove last point, which is starting
|
|
985
1177
|
# a new path
|
|
986
1178
|
self.points = self.points[:-1]
|
|
987
|
-
self.append_points(
|
|
1179
|
+
self.append_points(vectorized_mobject.points)
|
|
988
1180
|
|
|
989
1181
|
def apply_function(self, function: MappingFunction) -> Self:
|
|
990
1182
|
factor = self.pre_function_handle_to_anchor_scale_factor
|
|
@@ -998,8 +1190,8 @@ class VMobject(Mobject):
|
|
|
998
1190
|
def rotate(
|
|
999
1191
|
self,
|
|
1000
1192
|
angle: float,
|
|
1001
|
-
axis:
|
|
1002
|
-
about_point:
|
|
1193
|
+
axis: Vector3D = OUT,
|
|
1194
|
+
about_point: Point3DLike | None = None,
|
|
1003
1195
|
**kwargs,
|
|
1004
1196
|
) -> Self:
|
|
1005
1197
|
self.rotate_sheen_direction(angle, axis)
|
|
@@ -1038,10 +1230,10 @@ class VMobject(Mobject):
|
|
|
1038
1230
|
return self
|
|
1039
1231
|
|
|
1040
1232
|
#
|
|
1041
|
-
def consider_points_equals(self, p0:
|
|
1233
|
+
def consider_points_equals(self, p0: Point3DLike, p1: Point3DLike) -> bool:
|
|
1042
1234
|
return np.allclose(p0, p1, atol=self.tolerance_for_point_equality)
|
|
1043
1235
|
|
|
1044
|
-
def consider_points_equals_2d(self, p0:
|
|
1236
|
+
def consider_points_equals_2d(self, p0: Point2DLike, p1: Point2DLike) -> bool:
|
|
1045
1237
|
"""Determine if two points are close enough to be considered equal.
|
|
1046
1238
|
|
|
1047
1239
|
This uses the algorithm from np.isclose(), but expanded here for the
|
|
@@ -1062,19 +1254,17 @@ class VMobject(Mobject):
|
|
|
1062
1254
|
atol = self.tolerance_for_point_equality
|
|
1063
1255
|
if abs(p0[0] - p1[0]) > atol + rtol * abs(p1[0]):
|
|
1064
1256
|
return False
|
|
1065
|
-
|
|
1066
|
-
return False
|
|
1067
|
-
return True
|
|
1257
|
+
return abs(p0[1] - p1[1]) <= atol + rtol * abs(p1[1])
|
|
1068
1258
|
|
|
1069
1259
|
# Information about line
|
|
1070
1260
|
def get_cubic_bezier_tuples_from_points(
|
|
1071
|
-
self, points:
|
|
1072
|
-
) ->
|
|
1261
|
+
self, points: CubicBezierPathLike
|
|
1262
|
+
) -> CubicBezierPoints_Array:
|
|
1073
1263
|
return np.array(self.gen_cubic_bezier_tuples_from_points(points))
|
|
1074
1264
|
|
|
1075
1265
|
def gen_cubic_bezier_tuples_from_points(
|
|
1076
|
-
self, points:
|
|
1077
|
-
) -> tuple[
|
|
1266
|
+
self, points: CubicBezierPathLike
|
|
1267
|
+
) -> tuple[CubicBezierPointsLike, ...]:
|
|
1078
1268
|
"""Returns the bezier tuples from an array of points.
|
|
1079
1269
|
|
|
1080
1270
|
self.points is a list of the anchors and handles of the bezier curves of the mobject (ie [anchor1, handle1, handle2, anchor2, anchor3 ..])
|
|
@@ -1098,14 +1288,14 @@ class VMobject(Mobject):
|
|
|
1098
1288
|
# Basically take every nppcc element.
|
|
1099
1289
|
return tuple(points[i : i + nppcc] for i in range(0, len(points), nppcc))
|
|
1100
1290
|
|
|
1101
|
-
def get_cubic_bezier_tuples(self) ->
|
|
1291
|
+
def get_cubic_bezier_tuples(self) -> CubicBezierPoints_Array:
|
|
1102
1292
|
return self.get_cubic_bezier_tuples_from_points(self.points)
|
|
1103
1293
|
|
|
1104
1294
|
def _gen_subpaths_from_points(
|
|
1105
1295
|
self,
|
|
1106
|
-
points:
|
|
1296
|
+
points: CubicBezierPath,
|
|
1107
1297
|
filter_func: Callable[[int], bool],
|
|
1108
|
-
) ->
|
|
1298
|
+
) -> Iterable[CubicSpline]:
|
|
1109
1299
|
"""Given an array of points defining the bezier curves of the vmobject, return subpaths formed by these points.
|
|
1110
1300
|
Here, Two bezier curves form a path if at least two of their anchors are evaluated True by the relation defined by filter_func.
|
|
1111
1301
|
|
|
@@ -1123,7 +1313,7 @@ class VMobject(Mobject):
|
|
|
1123
1313
|
|
|
1124
1314
|
Returns
|
|
1125
1315
|
-------
|
|
1126
|
-
|
|
1316
|
+
Iterable[CubicSpline]
|
|
1127
1317
|
subpaths formed by the points.
|
|
1128
1318
|
"""
|
|
1129
1319
|
nppcc = self.n_points_per_cubic_curve
|
|
@@ -1135,7 +1325,7 @@ class VMobject(Mobject):
|
|
|
1135
1325
|
if (i2 - i1) >= nppcc
|
|
1136
1326
|
)
|
|
1137
1327
|
|
|
1138
|
-
def get_subpaths_from_points(self, points:
|
|
1328
|
+
def get_subpaths_from_points(self, points: CubicBezierPath) -> list[CubicSpline]:
|
|
1139
1329
|
return list(
|
|
1140
1330
|
self._gen_subpaths_from_points(
|
|
1141
1331
|
points,
|
|
@@ -1144,26 +1334,26 @@ class VMobject(Mobject):
|
|
|
1144
1334
|
)
|
|
1145
1335
|
|
|
1146
1336
|
def gen_subpaths_from_points_2d(
|
|
1147
|
-
self, points:
|
|
1148
|
-
) ->
|
|
1337
|
+
self, points: CubicBezierPath
|
|
1338
|
+
) -> Iterable[CubicSpline]:
|
|
1149
1339
|
return self._gen_subpaths_from_points(
|
|
1150
1340
|
points,
|
|
1151
1341
|
lambda n: not self.consider_points_equals_2d(points[n - 1], points[n]),
|
|
1152
1342
|
)
|
|
1153
1343
|
|
|
1154
|
-
def get_subpaths(self) -> list[
|
|
1344
|
+
def get_subpaths(self) -> list[CubicSpline]:
|
|
1155
1345
|
"""Returns subpaths formed by the curves of the VMobject.
|
|
1156
1346
|
|
|
1157
1347
|
Subpaths are ranges of curves with each pair of consecutive curves having their end/start points coincident.
|
|
1158
1348
|
|
|
1159
1349
|
Returns
|
|
1160
1350
|
-------
|
|
1161
|
-
list[
|
|
1351
|
+
list[CubicSpline]
|
|
1162
1352
|
subpaths.
|
|
1163
1353
|
"""
|
|
1164
1354
|
return self.get_subpaths_from_points(self.points)
|
|
1165
1355
|
|
|
1166
|
-
def get_nth_curve_points(self, n: int) ->
|
|
1356
|
+
def get_nth_curve_points(self, n: int) -> CubicBezierPoints:
|
|
1167
1357
|
"""Returns the points defining the nth curve of the vmobject.
|
|
1168
1358
|
|
|
1169
1359
|
Parameters
|
|
@@ -1173,7 +1363,7 @@ class VMobject(Mobject):
|
|
|
1173
1363
|
|
|
1174
1364
|
Returns
|
|
1175
1365
|
-------
|
|
1176
|
-
|
|
1366
|
+
CubicBezierPoints
|
|
1177
1367
|
points defining the nth bezier curve (anchors, handles)
|
|
1178
1368
|
"""
|
|
1179
1369
|
assert n < self.get_num_curves()
|
|
@@ -1242,7 +1432,6 @@ class VMobject(Mobject):
|
|
|
1242
1432
|
length : :class:`float`
|
|
1243
1433
|
The length of the nth curve.
|
|
1244
1434
|
"""
|
|
1245
|
-
|
|
1246
1435
|
_, length = self.get_nth_curve_function_with_length(n, sample_points)
|
|
1247
1436
|
|
|
1248
1437
|
return length
|
|
@@ -1268,7 +1457,6 @@ class VMobject(Mobject):
|
|
|
1268
1457
|
length : :class:`float`
|
|
1269
1458
|
The length of the nth curve.
|
|
1270
1459
|
"""
|
|
1271
|
-
|
|
1272
1460
|
curve = self.get_nth_curve_function(n)
|
|
1273
1461
|
norms = self.get_nth_curve_length_pieces(n, sample_points=sample_points)
|
|
1274
1462
|
length = np.sum(norms)
|
|
@@ -1288,15 +1476,14 @@ class VMobject(Mobject):
|
|
|
1288
1476
|
|
|
1289
1477
|
def get_curve_functions(
|
|
1290
1478
|
self,
|
|
1291
|
-
) ->
|
|
1479
|
+
) -> Iterable[Callable[[float], Point3D]]:
|
|
1292
1480
|
"""Gets the functions for the curves of the mobject.
|
|
1293
1481
|
|
|
1294
1482
|
Returns
|
|
1295
1483
|
-------
|
|
1296
|
-
|
|
1484
|
+
Iterable[Callable[[float], Point3D]]
|
|
1297
1485
|
The functions for the curves.
|
|
1298
1486
|
"""
|
|
1299
|
-
|
|
1300
1487
|
num_curves = self.get_num_curves()
|
|
1301
1488
|
|
|
1302
1489
|
for n in range(num_curves):
|
|
@@ -1304,7 +1491,7 @@ class VMobject(Mobject):
|
|
|
1304
1491
|
|
|
1305
1492
|
def get_curve_functions_with_lengths(
|
|
1306
1493
|
self, **kwargs
|
|
1307
|
-
) ->
|
|
1494
|
+
) -> Iterable[tuple[Callable[[float], Point3D], float]]:
|
|
1308
1495
|
"""Gets the functions and lengths of the curves for the mobject.
|
|
1309
1496
|
|
|
1310
1497
|
Parameters
|
|
@@ -1314,10 +1501,9 @@ class VMobject(Mobject):
|
|
|
1314
1501
|
|
|
1315
1502
|
Returns
|
|
1316
1503
|
-------
|
|
1317
|
-
|
|
1504
|
+
Iterable[tuple[Callable[[float], Point3D], float]]
|
|
1318
1505
|
The functions and lengths of the curves.
|
|
1319
1506
|
"""
|
|
1320
|
-
|
|
1321
1507
|
num_curves = self.get_num_curves()
|
|
1322
1508
|
|
|
1323
1509
|
for n in range(num_curves):
|
|
@@ -1342,8 +1528,23 @@ class VMobject(Mobject):
|
|
|
1342
1528
|
If ``alpha`` is not between 0 and 1.
|
|
1343
1529
|
:exc:`Exception`
|
|
1344
1530
|
If the :class:`VMobject` has no points.
|
|
1345
|
-
"""
|
|
1346
1531
|
|
|
1532
|
+
Example
|
|
1533
|
+
-------
|
|
1534
|
+
.. manim:: PointFromProportion
|
|
1535
|
+
:save_last_frame:
|
|
1536
|
+
|
|
1537
|
+
class PointFromProportion(Scene):
|
|
1538
|
+
def construct(self):
|
|
1539
|
+
line = Line(2*DL, 2*UR)
|
|
1540
|
+
self.add(line)
|
|
1541
|
+
colors = (RED, BLUE, YELLOW)
|
|
1542
|
+
proportions = (1/4, 1/2, 3/4)
|
|
1543
|
+
for color, proportion in zip(colors, proportions):
|
|
1544
|
+
self.add(Dot(color=color).move_to(
|
|
1545
|
+
line.point_from_proportion(proportion)
|
|
1546
|
+
))
|
|
1547
|
+
"""
|
|
1347
1548
|
if alpha < 0 or alpha > 1:
|
|
1348
1549
|
raise ValueError(f"Alpha {alpha} not between 0 and 1.")
|
|
1349
1550
|
|
|
@@ -1366,10 +1567,13 @@ class VMobject(Mobject):
|
|
|
1366
1567
|
return curve(residue)
|
|
1367
1568
|
|
|
1368
1569
|
current_length += length
|
|
1570
|
+
raise Exception(
|
|
1571
|
+
"Not sure how you reached here, please file a bug report at https://github.com/ManimCommunity/manim/issues/new/choose"
|
|
1572
|
+
)
|
|
1369
1573
|
|
|
1370
1574
|
def proportion_from_point(
|
|
1371
1575
|
self,
|
|
1372
|
-
point:
|
|
1576
|
+
point: Point3DLike,
|
|
1373
1577
|
) -> float:
|
|
1374
1578
|
"""Returns the proportion along the path of the :class:`VMobject`
|
|
1375
1579
|
a particular given point is at.
|
|
@@ -1458,7 +1662,7 @@ class VMobject(Mobject):
|
|
|
1458
1662
|
nppcc = self.n_points_per_cubic_curve
|
|
1459
1663
|
return self.points[nppcc - 1 :: nppcc]
|
|
1460
1664
|
|
|
1461
|
-
def get_anchors(self) ->
|
|
1665
|
+
def get_anchors(self) -> list[Point3D]:
|
|
1462
1666
|
"""Returns the anchors of the curves forming the VMobject.
|
|
1463
1667
|
|
|
1464
1668
|
Returns
|
|
@@ -1468,9 +1672,10 @@ class VMobject(Mobject):
|
|
|
1468
1672
|
"""
|
|
1469
1673
|
if self.points.shape[0] == 1:
|
|
1470
1674
|
return self.points
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
)
|
|
1675
|
+
|
|
1676
|
+
s = self.get_start_anchors()
|
|
1677
|
+
e = self.get_end_anchors()
|
|
1678
|
+
return list(it.chain.from_iterable(zip(s, e)))
|
|
1474
1679
|
|
|
1475
1680
|
def get_points_defining_boundary(self) -> Point3D_Array:
|
|
1476
1681
|
# Probably returns all anchors, but this is weird regarding the name of the method.
|
|
@@ -1491,7 +1696,6 @@ class VMobject(Mobject):
|
|
|
1491
1696
|
float
|
|
1492
1697
|
The length of the :class:`VMobject`.
|
|
1493
1698
|
"""
|
|
1494
|
-
|
|
1495
1699
|
return sum(
|
|
1496
1700
|
length
|
|
1497
1701
|
for _, length in self.get_curve_functions_with_lengths(
|
|
@@ -1596,8 +1800,8 @@ class VMobject(Mobject):
|
|
|
1596
1800
|
return self
|
|
1597
1801
|
|
|
1598
1802
|
def insert_n_curves_to_point_list(
|
|
1599
|
-
self, n: int, points:
|
|
1600
|
-
) ->
|
|
1803
|
+
self, n: int, points: BezierPathLike
|
|
1804
|
+
) -> BezierPath:
|
|
1601
1805
|
"""Given an array of k points defining a bezier curves (anchors and handles), returns points defining exactly k + n bezier curves.
|
|
1602
1806
|
|
|
1603
1807
|
Parameters
|
|
@@ -1611,44 +1815,14 @@ class VMobject(Mobject):
|
|
|
1611
1815
|
-------
|
|
1612
1816
|
Points generated.
|
|
1613
1817
|
"""
|
|
1614
|
-
|
|
1615
1818
|
if len(points) == 1:
|
|
1616
1819
|
nppcc = self.n_points_per_cubic_curve
|
|
1617
1820
|
return np.repeat(points, nppcc * n, 0)
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
# it's total length is target_num. For example,
|
|
1624
|
-
# with curr_num = 10, target_num = 15, this would
|
|
1625
|
-
# be [0, 0, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9]
|
|
1626
|
-
repeat_indices = (np.arange(target_num, dtype="i") * curr_num) // target_num
|
|
1627
|
-
|
|
1628
|
-
# If the nth term of this list is k, it means
|
|
1629
|
-
# that the nth curve of our path should be split
|
|
1630
|
-
# into k pieces.
|
|
1631
|
-
# In the above example our array had the following elements
|
|
1632
|
-
# [0, 0, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9]
|
|
1633
|
-
# We have two 0s, one 1, two 2s and so on.
|
|
1634
|
-
# The split factors array would hence be:
|
|
1635
|
-
# [2, 1, 2, 1, 2, 1, 2, 1, 2, 1]
|
|
1636
|
-
split_factors = np.zeros(curr_num, dtype="i")
|
|
1637
|
-
for val in repeat_indices:
|
|
1638
|
-
split_factors[val] += 1
|
|
1639
|
-
|
|
1640
|
-
new_points = np.zeros((0, self.dim))
|
|
1641
|
-
for quad, sf in zip(bezier_quads, split_factors):
|
|
1642
|
-
# What was once a single cubic curve defined
|
|
1643
|
-
# by "quad" will now be broken into sf
|
|
1644
|
-
# smaller cubic curves
|
|
1645
|
-
alphas = np.linspace(0, 1, sf + 1)
|
|
1646
|
-
for a1, a2 in zip(alphas, alphas[1:]):
|
|
1647
|
-
new_points = np.append(
|
|
1648
|
-
new_points,
|
|
1649
|
-
partial_bezier_points(quad, a1, a2),
|
|
1650
|
-
axis=0,
|
|
1651
|
-
)
|
|
1821
|
+
bezier_tuples = self.get_cubic_bezier_tuples_from_points(points)
|
|
1822
|
+
current_number_of_curves = len(bezier_tuples)
|
|
1823
|
+
new_number_of_curves = current_number_of_curves + n
|
|
1824
|
+
new_bezier_tuples = bezier_remap(bezier_tuples, new_number_of_curves)
|
|
1825
|
+
new_points = new_bezier_tuples.reshape(-1, 3)
|
|
1652
1826
|
return new_points
|
|
1653
1827
|
|
|
1654
1828
|
def align_rgbas(self, vmobject: VMobject) -> Self:
|
|
@@ -1664,7 +1838,7 @@ class VMobject(Mobject):
|
|
|
1664
1838
|
setattr(self, attr, new_a1)
|
|
1665
1839
|
return self
|
|
1666
1840
|
|
|
1667
|
-
def get_point_mobject(self, center:
|
|
1841
|
+
def get_point_mobject(self, center: Point3DLike | None = None) -> VectorizedPoint:
|
|
1668
1842
|
if center is None:
|
|
1669
1843
|
center = self.get_center()
|
|
1670
1844
|
point = VectorizedPoint(center)
|
|
@@ -1690,7 +1864,10 @@ class VMobject(Mobject):
|
|
|
1690
1864
|
interpolate(getattr(mobject1, attr), getattr(mobject2, attr), alpha),
|
|
1691
1865
|
)
|
|
1692
1866
|
if alpha == 1.0:
|
|
1693
|
-
|
|
1867
|
+
val = getattr(mobject2, attr)
|
|
1868
|
+
if isinstance(val, np.ndarray):
|
|
1869
|
+
val = val.copy()
|
|
1870
|
+
setattr(self, attr, val)
|
|
1694
1871
|
|
|
1695
1872
|
def pointwise_become_partial(
|
|
1696
1873
|
self,
|
|
@@ -1698,60 +1875,91 @@ class VMobject(Mobject):
|
|
|
1698
1875
|
a: float,
|
|
1699
1876
|
b: float,
|
|
1700
1877
|
) -> Self:
|
|
1701
|
-
"""Given
|
|
1702
|
-
|
|
1878
|
+
"""Given a 2nd :class:`.VMobject` ``vmobject``, a lower bound ``a`` and
|
|
1879
|
+
an upper bound ``b``, modify this :class:`.VMobject`'s points to
|
|
1880
|
+
match the portion of the Bézier spline described by ``vmobject.points``
|
|
1881
|
+
with the parameter ``t`` between ``a`` and ``b``.
|
|
1703
1882
|
|
|
1704
1883
|
Parameters
|
|
1705
1884
|
----------
|
|
1706
1885
|
vmobject
|
|
1707
|
-
The
|
|
1886
|
+
The :class:`.VMobject` that will serve as a model.
|
|
1708
1887
|
a
|
|
1709
|
-
|
|
1888
|
+
The lower bound for ``t``.
|
|
1710
1889
|
b
|
|
1711
|
-
|
|
1890
|
+
The upper bound for ``t``
|
|
1712
1891
|
|
|
1713
1892
|
Returns
|
|
1714
1893
|
-------
|
|
1715
|
-
:class
|
|
1716
|
-
|
|
1894
|
+
:class:`.VMobject`
|
|
1895
|
+
The :class:`.VMobject` itself, after the transformation.
|
|
1896
|
+
|
|
1897
|
+
Raises
|
|
1898
|
+
------
|
|
1899
|
+
TypeError
|
|
1900
|
+
If ``vmobject`` is not an instance of :class:`VMobject`.
|
|
1717
1901
|
"""
|
|
1718
|
-
|
|
1902
|
+
if not isinstance(vmobject, VMobject):
|
|
1903
|
+
raise TypeError(
|
|
1904
|
+
f"Expected a VMobject, got value {vmobject} of type "
|
|
1905
|
+
f"{type(vmobject).__name__}."
|
|
1906
|
+
)
|
|
1719
1907
|
# Partial curve includes three portions:
|
|
1720
|
-
# - A middle section, which matches the curve exactly
|
|
1721
|
-
# - A start, which is some ending portion of an inner cubic
|
|
1722
|
-
# - An end, which is the starting portion of a later inner cubic
|
|
1908
|
+
# - A middle section, which matches the curve exactly.
|
|
1909
|
+
# - A start, which is some ending portion of an inner cubic.
|
|
1910
|
+
# - An end, which is the starting portion of a later inner cubic.
|
|
1723
1911
|
if a <= 0 and b >= 1:
|
|
1724
1912
|
self.set_points(vmobject.points)
|
|
1725
1913
|
return self
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
# The following two lines will compute which bezier curves of the given mobject need to be processed.
|
|
1730
|
-
# The residue basically indicates de proportion of the selected bezier curve that have to be selected.
|
|
1731
|
-
# Ex : if lower_index is 3, and lower_residue is 0.4, then the algorithm will append to the points 0.4 of the third bezier curve
|
|
1732
|
-
lower_index, lower_residue = integer_interpolate(0, num_cubics, a)
|
|
1733
|
-
upper_index, upper_residue = integer_interpolate(0, num_cubics, b)
|
|
1734
|
-
|
|
1735
|
-
self.clear_points()
|
|
1736
|
-
if num_cubics == 0:
|
|
1914
|
+
num_curves = vmobject.get_num_curves()
|
|
1915
|
+
if num_curves == 0:
|
|
1916
|
+
self.clear_points()
|
|
1737
1917
|
return self
|
|
1918
|
+
|
|
1919
|
+
# The following two lines will compute which Bézier curves of the given Mobject must be processed.
|
|
1920
|
+
# The residue indicates the proportion of the selected Bézier curve which must be selected.
|
|
1921
|
+
#
|
|
1922
|
+
# Example: if num_curves is 10, a is 0.34 and b is 0.78, then:
|
|
1923
|
+
# - lower_index is 3 and lower_residue is 0.4, which means the algorithm will look at the 3rd Bézier
|
|
1924
|
+
# and select its part which ranges from t=0.4 to t=1.
|
|
1925
|
+
# - upper_index is 7 and upper_residue is 0.8, which means the algorithm will look at the 7th Bézier
|
|
1926
|
+
# and select its part which ranges from t=0 to t=0.8.
|
|
1927
|
+
lower_index, lower_residue = integer_interpolate(0, num_curves, a)
|
|
1928
|
+
upper_index, upper_residue = integer_interpolate(0, num_curves, b)
|
|
1929
|
+
|
|
1930
|
+
nppc = self.n_points_per_curve
|
|
1931
|
+
# If both indices coincide, get a part of a single Bézier curve.
|
|
1738
1932
|
if lower_index == upper_index:
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1933
|
+
# Look at the "lower_index"-th Bézier curve and select its part from
|
|
1934
|
+
# t=lower_residue to t=upper_residue.
|
|
1935
|
+
self.points = partial_bezier_points(
|
|
1936
|
+
vmobject.points[nppc * lower_index : nppc * (lower_index + 1)],
|
|
1937
|
+
lower_residue,
|
|
1938
|
+
upper_residue,
|
|
1745
1939
|
)
|
|
1746
1940
|
else:
|
|
1747
|
-
|
|
1748
|
-
|
|
1941
|
+
# Allocate space for (upper_index-lower_index+1) Bézier curves.
|
|
1942
|
+
self.points = np.empty((nppc * (upper_index - lower_index + 1), self.dim))
|
|
1943
|
+
# Look at the "lower_index"-th Bezier curve and select its part from
|
|
1944
|
+
# t=lower_residue to t=1. This is the first curve in self.points.
|
|
1945
|
+
self.points[:nppc] = partial_bezier_points(
|
|
1946
|
+
vmobject.points[nppc * lower_index : nppc * (lower_index + 1)],
|
|
1947
|
+
lower_residue,
|
|
1948
|
+
1,
|
|
1749
1949
|
)
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
self.
|
|
1753
|
-
|
|
1950
|
+
# If there are more curves between the "lower_index"-th and the
|
|
1951
|
+
# "upper_index"-th Béziers, add them all to self.points.
|
|
1952
|
+
self.points[nppc:-nppc] = vmobject.points[
|
|
1953
|
+
nppc * (lower_index + 1) : nppc * upper_index
|
|
1954
|
+
]
|
|
1955
|
+
# Look at the "upper_index"-th Bézier curve and select its part from
|
|
1956
|
+
# t=0 to t=upper_residue. This is the last curve in self.points.
|
|
1957
|
+
self.points[-nppc:] = partial_bezier_points(
|
|
1958
|
+
vmobject.points[nppc * upper_index : nppc * (upper_index + 1)],
|
|
1959
|
+
0,
|
|
1960
|
+
upper_residue,
|
|
1754
1961
|
)
|
|
1962
|
+
|
|
1755
1963
|
return self
|
|
1756
1964
|
|
|
1757
1965
|
def get_subcurve(self, a: float, b: float) -> Self:
|
|
@@ -1870,19 +2078,21 @@ class VGroup(VMobject, metaclass=ConvertToOpenGL):
|
|
|
1870
2078
|
>>> triangle, square = Triangle(), Square()
|
|
1871
2079
|
>>> vg.add(triangle)
|
|
1872
2080
|
VGroup(Triangle)
|
|
1873
|
-
>>> vg + square
|
|
2081
|
+
>>> vg + square # a new VGroup is constructed
|
|
1874
2082
|
VGroup(Triangle, Square)
|
|
1875
|
-
>>> vg
|
|
2083
|
+
>>> vg # not modified
|
|
1876
2084
|
VGroup(Triangle)
|
|
1877
|
-
>>> vg += square
|
|
2085
|
+
>>> vg += square
|
|
2086
|
+
>>> vg # modifies vg
|
|
1878
2087
|
VGroup(Triangle, Square)
|
|
1879
2088
|
>>> vg.remove(triangle)
|
|
1880
2089
|
VGroup(Square)
|
|
1881
|
-
>>> vg - square
|
|
2090
|
+
>>> vg - square # a new VGroup is constructed
|
|
1882
2091
|
VGroup()
|
|
1883
|
-
>>> vg
|
|
2092
|
+
>>> vg # not modified
|
|
1884
2093
|
VGroup(Square)
|
|
1885
|
-
>>> vg -= square
|
|
2094
|
+
>>> vg -= square
|
|
2095
|
+
>>> vg # modifies vg
|
|
1886
2096
|
VGroup()
|
|
1887
2097
|
|
|
1888
2098
|
.. manim:: ArcShapeIris
|
|
@@ -1902,12 +2112,14 @@ class VGroup(VMobject, metaclass=ConvertToOpenGL):
|
|
|
1902
2112
|
|
|
1903
2113
|
"""
|
|
1904
2114
|
|
|
1905
|
-
def __init__(
|
|
2115
|
+
def __init__(
|
|
2116
|
+
self, *vmobjects: VMobject | Iterable[VMobject], **kwargs: Any
|
|
2117
|
+
) -> None:
|
|
1906
2118
|
super().__init__(**kwargs)
|
|
1907
2119
|
self.add(*vmobjects)
|
|
1908
2120
|
|
|
1909
2121
|
def __repr__(self) -> str:
|
|
1910
|
-
return f
|
|
2122
|
+
return f"{self.__class__.__name__}({', '.join(str(mob) for mob in self.submobjects)})"
|
|
1911
2123
|
|
|
1912
2124
|
def __str__(self) -> str:
|
|
1913
2125
|
return (
|
|
@@ -1915,13 +2127,16 @@ class VGroup(VMobject, metaclass=ConvertToOpenGL):
|
|
|
1915
2127
|
f"submobject{'s' if len(self.submobjects) > 0 else ''}"
|
|
1916
2128
|
)
|
|
1917
2129
|
|
|
1918
|
-
def add(
|
|
1919
|
-
|
|
2130
|
+
def add(
|
|
2131
|
+
self,
|
|
2132
|
+
*vmobjects: VMobject | Iterable[VMobject],
|
|
2133
|
+
) -> Self:
|
|
2134
|
+
"""Checks if all passed elements are an instance, or iterables of VMobject and then adds them to submobjects
|
|
1920
2135
|
|
|
1921
2136
|
Parameters
|
|
1922
2137
|
----------
|
|
1923
2138
|
vmobjects
|
|
1924
|
-
List of
|
|
2139
|
+
List or iterable of VMobjects to add
|
|
1925
2140
|
|
|
1926
2141
|
Returns
|
|
1927
2142
|
-------
|
|
@@ -1930,10 +2145,13 @@ class VGroup(VMobject, metaclass=ConvertToOpenGL):
|
|
|
1930
2145
|
Raises
|
|
1931
2146
|
------
|
|
1932
2147
|
TypeError
|
|
1933
|
-
If one element of the list is not an instance of VMobject
|
|
2148
|
+
If one element of the list, or iterable is not an instance of VMobject
|
|
1934
2149
|
|
|
1935
2150
|
Examples
|
|
1936
2151
|
--------
|
|
2152
|
+
The following example shows how to add individual or multiple `VMobject` instances through the `VGroup`
|
|
2153
|
+
constructor and its `.add()` method.
|
|
2154
|
+
|
|
1937
2155
|
.. manim:: AddToVGroup
|
|
1938
2156
|
|
|
1939
2157
|
class AddToVGroup(Scene):
|
|
@@ -1962,10 +2180,65 @@ class VGroup(VMobject, metaclass=ConvertToOpenGL):
|
|
|
1962
2180
|
self.play( # Animate group without component
|
|
1963
2181
|
(gr-circle_red).animate.shift(RIGHT)
|
|
1964
2182
|
)
|
|
2183
|
+
|
|
2184
|
+
A `VGroup` can be created using iterables as well. Keep in mind that all generated values from an
|
|
2185
|
+
iterable must be an instance of `VMobject`. This is demonstrated below:
|
|
2186
|
+
|
|
2187
|
+
.. manim:: AddIterableToVGroupExample
|
|
2188
|
+
:save_last_frame:
|
|
2189
|
+
|
|
2190
|
+
class AddIterableToVGroupExample(Scene):
|
|
2191
|
+
def construct(self):
|
|
2192
|
+
v = VGroup(
|
|
2193
|
+
Square(), # Singular VMobject instance
|
|
2194
|
+
[Circle(), Triangle()], # List of VMobject instances
|
|
2195
|
+
Dot(),
|
|
2196
|
+
(Dot() for _ in range(2)), # Iterable that generates VMobjects
|
|
2197
|
+
)
|
|
2198
|
+
v.arrange()
|
|
2199
|
+
self.add(v)
|
|
2200
|
+
|
|
2201
|
+
To facilitate this, the iterable is unpacked before its individual instances are added to the `VGroup`.
|
|
2202
|
+
As a result, when you index a `VGroup`, you will never get back an iterable.
|
|
2203
|
+
Instead, you will always receive `VMobject` instances, including those
|
|
2204
|
+
that were part of the iterable/s that you originally added to the `VGroup`.
|
|
1965
2205
|
"""
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
2206
|
+
|
|
2207
|
+
def get_type_error_message(invalid_obj, invalid_indices):
|
|
2208
|
+
return (
|
|
2209
|
+
f"Only values of type {vmobject_render_type.__name__} can be added "
|
|
2210
|
+
"as submobjects of VGroup, but the value "
|
|
2211
|
+
f"{repr(invalid_obj)} (at index {invalid_indices[1]} of "
|
|
2212
|
+
f"parameter {invalid_indices[0]}) is of type "
|
|
2213
|
+
f"{type(invalid_obj).__name__}."
|
|
2214
|
+
)
|
|
2215
|
+
|
|
2216
|
+
vmobject_render_type = (
|
|
2217
|
+
OpenGLVMobject if config.renderer == RendererType.OPENGL else VMobject
|
|
2218
|
+
)
|
|
2219
|
+
valid_vmobjects = []
|
|
2220
|
+
|
|
2221
|
+
for i, vmobject in enumerate(vmobjects):
|
|
2222
|
+
if isinstance(vmobject, vmobject_render_type):
|
|
2223
|
+
valid_vmobjects.append(vmobject)
|
|
2224
|
+
elif isinstance(vmobject, Iterable) and not isinstance(
|
|
2225
|
+
vmobject, (Mobject, OpenGLMobject)
|
|
2226
|
+
):
|
|
2227
|
+
for j, subvmobject in enumerate(vmobject):
|
|
2228
|
+
if not isinstance(subvmobject, vmobject_render_type):
|
|
2229
|
+
raise TypeError(get_type_error_message(subvmobject, (i, j)))
|
|
2230
|
+
valid_vmobjects.append(subvmobject)
|
|
2231
|
+
elif isinstance(vmobject, Iterable) and isinstance(
|
|
2232
|
+
vmobject, (Mobject, OpenGLMobject)
|
|
2233
|
+
):
|
|
2234
|
+
raise TypeError(
|
|
2235
|
+
f"{get_type_error_message(vmobject, (i, 0))} "
|
|
2236
|
+
"You can try adding this value into a Group instead."
|
|
2237
|
+
)
|
|
2238
|
+
else:
|
|
2239
|
+
raise TypeError(get_type_error_message(vmobject, (i, 0)))
|
|
2240
|
+
|
|
2241
|
+
return super().add(*valid_vmobjects)
|
|
1969
2242
|
|
|
1970
2243
|
def __add__(self, vmobject: VMobject) -> Self:
|
|
1971
2244
|
return VGroup(*self.submobjects, vmobject)
|
|
@@ -2002,8 +2275,7 @@ class VGroup(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2002
2275
|
>>> new_obj = VMobject()
|
|
2003
2276
|
>>> vgroup[0] = new_obj
|
|
2004
2277
|
"""
|
|
2005
|
-
|
|
2006
|
-
raise TypeError("All submobjects must be of type VMobject")
|
|
2278
|
+
self._assert_valid_submobjects(tuplify(value))
|
|
2007
2279
|
self.submobjects[key] = value
|
|
2008
2280
|
|
|
2009
2281
|
|
|
@@ -2147,7 +2419,7 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2147
2419
|
Normal usage::
|
|
2148
2420
|
|
|
2149
2421
|
square_obj = Square()
|
|
2150
|
-
my_dict.add([(
|
|
2422
|
+
my_dict.add([("s", square_obj)])
|
|
2151
2423
|
"""
|
|
2152
2424
|
for key, value in dict(mapping_or_iterable).items():
|
|
2153
2425
|
self.add_key_value_pair(key, value)
|
|
@@ -2174,10 +2446,10 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2174
2446
|
--------
|
|
2175
2447
|
Normal usage::
|
|
2176
2448
|
|
|
2177
|
-
my_dict.remove(
|
|
2449
|
+
my_dict.remove("square")
|
|
2178
2450
|
"""
|
|
2179
2451
|
if key not in self.submob_dict:
|
|
2180
|
-
raise KeyError("The given key '
|
|
2452
|
+
raise KeyError(f"The given key '{key!s}' is not present in the VDict")
|
|
2181
2453
|
super().remove(self.submob_dict[key])
|
|
2182
2454
|
del self.submob_dict[key]
|
|
2183
2455
|
return self
|
|
@@ -2199,7 +2471,7 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2199
2471
|
--------
|
|
2200
2472
|
Normal usage::
|
|
2201
2473
|
|
|
2202
|
-
self.play(Create(my_dict[
|
|
2474
|
+
self.play(Create(my_dict["s"]))
|
|
2203
2475
|
"""
|
|
2204
2476
|
submob = self.submob_dict[key]
|
|
2205
2477
|
return submob
|
|
@@ -2223,7 +2495,7 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2223
2495
|
Normal usage::
|
|
2224
2496
|
|
|
2225
2497
|
square_obj = Square()
|
|
2226
|
-
my_dict[
|
|
2498
|
+
my_dict["sq"] = square_obj
|
|
2227
2499
|
"""
|
|
2228
2500
|
if key in self.submob_dict:
|
|
2229
2501
|
self.remove(key)
|
|
@@ -2328,11 +2600,10 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2328
2600
|
Normal usage::
|
|
2329
2601
|
|
|
2330
2602
|
square_obj = Square()
|
|
2331
|
-
self.add_key_value_pair(
|
|
2603
|
+
self.add_key_value_pair("s", square_obj)
|
|
2332
2604
|
|
|
2333
2605
|
"""
|
|
2334
|
-
|
|
2335
|
-
raise TypeError("All submobjects must be of type VMobject")
|
|
2606
|
+
self._assert_valid_submobjects([value])
|
|
2336
2607
|
mob = value
|
|
2337
2608
|
if self.show_keys:
|
|
2338
2609
|
# This import is here and not at the top to avoid circular import
|
|
@@ -2348,7 +2619,7 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2348
2619
|
class VectorizedPoint(VMobject, metaclass=ConvertToOpenGL):
|
|
2349
2620
|
def __init__(
|
|
2350
2621
|
self,
|
|
2351
|
-
location:
|
|
2622
|
+
location: Point3DLike = ORIGIN,
|
|
2352
2623
|
color: ManimColor = BLACK,
|
|
2353
2624
|
fill_opacity: float = 0,
|
|
2354
2625
|
stroke_width: float = 0,
|
|
@@ -2458,7 +2729,7 @@ class CurvesAsSubmobjects(VGroup):
|
|
|
2458
2729
|
if len(self.submobjects) == 0:
|
|
2459
2730
|
caller_name = sys._getframe(1).f_code.co_name
|
|
2460
2731
|
raise Exception(
|
|
2461
|
-
f"Cannot call CurvesAsSubmobjects.{caller_name} for a CurvesAsSubmobject with no submobjects"
|
|
2732
|
+
f"Cannot call CurvesAsSubmobjects. {caller_name} for a CurvesAsSubmobject with no submobjects"
|
|
2462
2733
|
)
|
|
2463
2734
|
|
|
2464
2735
|
def _get_submobjects_with_points(self):
|
|
@@ -2468,7 +2739,7 @@ class CurvesAsSubmobjects(VGroup):
|
|
|
2468
2739
|
if len(submobjs_with_pts) == 0:
|
|
2469
2740
|
caller_name = sys._getframe(1).f_code.co_name
|
|
2470
2741
|
raise Exception(
|
|
2471
|
-
f"Cannot call CurvesAsSubmobjects.{caller_name} for a CurvesAsSubmobject whose submobjects have no points"
|
|
2742
|
+
f"Cannot call CurvesAsSubmobjects. {caller_name} for a CurvesAsSubmobject whose submobjects have no points"
|
|
2472
2743
|
)
|
|
2473
2744
|
return submobjs_with_pts
|
|
2474
2745
|
|
|
@@ -2548,15 +2819,12 @@ class DashedVMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2548
2819
|
if vmobject.is_closed():
|
|
2549
2820
|
void_len = (1 - r) / n
|
|
2550
2821
|
else:
|
|
2551
|
-
if n == 1
|
|
2552
|
-
void_len = 1 - r
|
|
2553
|
-
else:
|
|
2554
|
-
void_len = (1 - r) / (n - 1)
|
|
2822
|
+
void_len = 1 - r if n == 1 else (1 - r) / (n - 1)
|
|
2555
2823
|
|
|
2556
2824
|
period = dash_len + void_len
|
|
2557
2825
|
phase_shift = (dash_offset % 1) * period
|
|
2558
2826
|
|
|
2559
|
-
if vmobject.is_closed():
|
|
2827
|
+
if vmobject.is_closed(): # noqa: SIM108
|
|
2560
2828
|
# closed curves have equal amount of dashes and voids
|
|
2561
2829
|
pattern_len = 1
|
|
2562
2830
|
else:
|