manim 0.18.1__py3-none-any.whl → 0.19.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of manim might be problematic. Click here for more details.
- manim/__main__.py +45 -12
- manim/_config/__init__.py +2 -2
- manim/_config/cli_colors.py +8 -4
- manim/_config/default.cfg +0 -2
- manim/_config/logger_utils.py +5 -0
- manim/_config/utils.py +29 -38
- manim/animation/animation.py +148 -8
- manim/animation/composition.py +16 -13
- manim/animation/creation.py +184 -8
- manim/animation/fading.py +5 -8
- manim/animation/indication.py +93 -26
- manim/animation/movement.py +21 -3
- manim/animation/rotation.py +2 -1
- manim/animation/specialized.py +3 -5
- manim/animation/speedmodifier.py +3 -3
- manim/animation/transform.py +4 -5
- manim/animation/updaters/mobject_update_utils.py +17 -14
- manim/camera/camera.py +2 -2
- manim/cli/__init__.py +17 -0
- manim/cli/cfg/group.py +52 -36
- manim/cli/checkhealth/checks.py +92 -76
- manim/cli/checkhealth/commands.py +12 -5
- manim/cli/default_group.py +148 -24
- manim/cli/init/commands.py +28 -23
- manim/cli/plugins/commands.py +13 -3
- manim/cli/render/commands.py +47 -42
- manim/cli/render/global_options.py +43 -9
- manim/cli/render/render_options.py +84 -19
- manim/constants.py +11 -4
- manim/mobject/frame.py +0 -1
- manim/mobject/geometry/arc.py +109 -75
- manim/mobject/geometry/boolean_ops.py +20 -17
- manim/mobject/geometry/labeled.py +300 -77
- manim/mobject/geometry/line.py +120 -60
- manim/mobject/geometry/polygram.py +109 -25
- manim/mobject/geometry/shape_matchers.py +35 -15
- manim/mobject/geometry/tips.py +36 -27
- manim/mobject/graph.py +48 -40
- manim/mobject/graphing/coordinate_systems.py +110 -45
- manim/mobject/graphing/functions.py +16 -10
- manim/mobject/graphing/number_line.py +23 -9
- manim/mobject/graphing/probability.py +2 -10
- manim/mobject/graphing/scale.py +6 -5
- manim/mobject/matrix.py +17 -19
- manim/mobject/mobject.py +149 -103
- manim/mobject/opengl/opengl_geometry.py +4 -8
- manim/mobject/opengl/opengl_mobject.py +506 -343
- manim/mobject/opengl/opengl_point_cloud_mobject.py +3 -7
- manim/mobject/opengl/opengl_surface.py +1 -2
- manim/mobject/opengl/opengl_vectorized_mobject.py +27 -65
- manim/mobject/svg/brace.py +61 -13
- manim/mobject/svg/svg_mobject.py +2 -1
- manim/mobject/table.py +11 -12
- manim/mobject/text/code_mobject.py +186 -550
- manim/mobject/text/numbers.py +7 -7
- manim/mobject/text/tex_mobject.py +22 -13
- manim/mobject/text/text_mobject.py +29 -20
- manim/mobject/three_d/polyhedra.py +98 -1
- manim/mobject/three_d/three_dimensions.py +59 -31
- manim/mobject/types/image_mobject.py +37 -23
- manim/mobject/types/point_cloud_mobject.py +103 -67
- manim/mobject/types/vectorized_mobject.py +387 -214
- manim/mobject/value_tracker.py +2 -1
- manim/mobject/vector_field.py +2 -4
- manim/opengl/__init__.py +3 -3
- manim/plugins/__init__.py +2 -3
- manim/plugins/plugins_flags.py +3 -3
- manim/renderer/cairo_renderer.py +11 -11
- manim/renderer/opengl_renderer.py +19 -20
- manim/renderer/shader.py +2 -3
- manim/renderer/shader_wrapper.py +3 -2
- manim/scene/moving_camera_scene.py +23 -0
- manim/scene/scene.py +72 -41
- manim/scene/scene_file_writer.py +313 -164
- manim/scene/section.py +15 -15
- manim/scene/three_d_scene.py +8 -15
- manim/scene/vector_space_scene.py +3 -6
- manim/typing.py +326 -66
- manim/utils/bezier.py +1658 -381
- manim/utils/caching.py +11 -5
- manim/utils/color/AS2700.py +2 -0
- manim/utils/color/BS381.py +2 -0
- manim/utils/color/DVIPSNAMES.py +96 -0
- manim/utils/color/SVGNAMES.py +179 -0
- manim/utils/color/X11.py +3 -0
- manim/utils/color/XKCD.py +2 -0
- manim/utils/color/__init__.py +8 -5
- manim/utils/color/core.py +818 -301
- manim/utils/color/manim_colors.py +7 -9
- manim/utils/commands.py +40 -19
- manim/utils/config_ops.py +18 -13
- manim/utils/debug.py +8 -6
- manim/utils/deprecation.py +92 -43
- manim/utils/docbuild/autoaliasattr_directive.py +45 -8
- manim/utils/docbuild/autocolor_directive.py +12 -13
- manim/utils/docbuild/manim_directive.py +35 -29
- manim/utils/docbuild/module_parsing.py +74 -27
- manim/utils/family.py +3 -3
- manim/utils/family_ops.py +12 -4
- manim/utils/file_ops.py +22 -16
- manim/utils/hashing.py +7 -7
- manim/utils/images.py +10 -4
- manim/utils/ipython_magic.py +12 -8
- manim/utils/iterables.py +161 -119
- manim/utils/module_ops.py +55 -19
- manim/utils/opengl.py +68 -23
- manim/utils/parameter_parsing.py +3 -2
- manim/utils/paths.py +11 -5
- manim/utils/polylabel.py +168 -0
- manim/utils/qhull.py +218 -0
- manim/utils/rate_functions.py +69 -32
- manim/utils/simple_functions.py +24 -15
- manim/utils/sounds.py +7 -1
- manim/utils/space_ops.py +48 -37
- manim/utils/testing/_frames_testers.py +13 -8
- manim/utils/testing/_show_diff.py +5 -3
- manim/utils/testing/_test_class_makers.py +33 -18
- manim/utils/testing/frames_comparison.py +20 -14
- manim/utils/tex.py +4 -2
- manim/utils/tex_file_writing.py +45 -45
- manim/utils/tex_templates.py +1 -1
- manim/utils/unit.py +6 -5
- {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/METADATA +16 -9
- manim-0.19.0.dist-info/RECORD +221 -0
- {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/WHEEL +1 -1
- manim-0.18.1.dist-info/RECORD +0 -217
- {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/LICENSE +0 -0
- {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/LICENSE.community +0 -0
- {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/entry_points.txt +0 -0
|
@@ -14,16 +14,8 @@ __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
21
|
from PIL.Image import Image
|
|
@@ -32,13 +24,15 @@ from manim import config
|
|
|
32
24
|
from manim.constants import *
|
|
33
25
|
from manim.mobject.mobject import Mobject
|
|
34
26
|
from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL
|
|
27
|
+
from manim.mobject.opengl.opengl_mobject import OpenGLMobject
|
|
35
28
|
from manim.mobject.opengl.opengl_vectorized_mobject import OpenGLVMobject
|
|
36
29
|
from manim.mobject.three_d.three_d_utils import (
|
|
37
30
|
get_3d_vmob_gradient_start_and_end_points,
|
|
38
31
|
)
|
|
39
32
|
from manim.utils.bezier import (
|
|
40
33
|
bezier,
|
|
41
|
-
|
|
34
|
+
bezier_remap,
|
|
35
|
+
get_smooth_cubic_bezier_handle_points,
|
|
42
36
|
integer_interpolate,
|
|
43
37
|
interpolate,
|
|
44
38
|
partial_bezier_points,
|
|
@@ -54,18 +48,22 @@ from manim.utils.iterables import (
|
|
|
54
48
|
from manim.utils.space_ops import rotate_vector, shoelace_direction
|
|
55
49
|
|
|
56
50
|
if TYPE_CHECKING:
|
|
51
|
+
from typing import Any
|
|
52
|
+
|
|
57
53
|
import numpy.typing as npt
|
|
58
54
|
from typing_extensions import Self
|
|
59
55
|
|
|
60
56
|
from manim.typing import (
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
CubicBezierPath,
|
|
58
|
+
CubicBezierPointsLike,
|
|
59
|
+
CubicSpline,
|
|
63
60
|
ManimFloat,
|
|
64
61
|
MappingFunction,
|
|
65
|
-
|
|
62
|
+
Point2DLike,
|
|
66
63
|
Point3D,
|
|
67
64
|
Point3D_Array,
|
|
68
|
-
|
|
65
|
+
Point3DLike,
|
|
66
|
+
Point3DLike_Array,
|
|
69
67
|
RGBA_Array_Float,
|
|
70
68
|
Vector3D,
|
|
71
69
|
Zeros,
|
|
@@ -79,16 +77,6 @@ if TYPE_CHECKING:
|
|
|
79
77
|
# - Think about length of self.points. Always 0 or 1 mod 4?
|
|
80
78
|
# That's kind of weird.
|
|
81
79
|
|
|
82
|
-
__all__ = [
|
|
83
|
-
"VMobject",
|
|
84
|
-
"VGroup",
|
|
85
|
-
"VDict",
|
|
86
|
-
"VectorizedPoint",
|
|
87
|
-
"CurvesAsSubmobjects",
|
|
88
|
-
"VectorizedPoint",
|
|
89
|
-
"DashedVMobject",
|
|
90
|
-
]
|
|
91
|
-
|
|
92
80
|
|
|
93
81
|
class VMobject(Mobject):
|
|
94
82
|
"""A vectorized mobject.
|
|
@@ -139,7 +127,7 @@ class VMobject(Mobject):
|
|
|
139
127
|
tolerance_for_point_equality: float = 1e-6,
|
|
140
128
|
n_points_per_cubic_curve: int = 4,
|
|
141
129
|
cap_style: CapStyleType = CapStyleType.AUTO,
|
|
142
|
-
**kwargs,
|
|
130
|
+
**kwargs: Any,
|
|
143
131
|
):
|
|
144
132
|
self.fill_opacity = fill_opacity
|
|
145
133
|
self.stroke_opacity = stroke_opacity
|
|
@@ -166,6 +154,9 @@ class VMobject(Mobject):
|
|
|
166
154
|
self.shade_in_3d: bool = shade_in_3d
|
|
167
155
|
self.tolerance_for_point_equality: float = tolerance_for_point_equality
|
|
168
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
|
+
)
|
|
169
160
|
self.cap_style: CapStyleType = cap_style
|
|
170
161
|
super().__init__(**kwargs)
|
|
171
162
|
self.submobjects: list[VMobject]
|
|
@@ -179,6 +170,9 @@ class VMobject(Mobject):
|
|
|
179
170
|
if stroke_color is not None:
|
|
180
171
|
self.stroke_color = ManimColor.parse(stroke_color)
|
|
181
172
|
|
|
173
|
+
def _assert_valid_submobjects(self, submobjects: Iterable[VMobject]) -> Self:
|
|
174
|
+
return self._assert_valid_submobjects_internal(submobjects, VMobject)
|
|
175
|
+
|
|
182
176
|
# OpenGL compatibility
|
|
183
177
|
@property
|
|
184
178
|
def n_points_per_curve(self) -> int:
|
|
@@ -478,6 +472,64 @@ class VMobject(Mobject):
|
|
|
478
472
|
self.set_stroke(opacity=opacity, family=family, background=True)
|
|
479
473
|
return self
|
|
480
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
|
+
|
|
481
533
|
def fade(self, darkness: float = 0.5, family: bool = True) -> Self:
|
|
482
534
|
factor = 1.0 - darkness
|
|
483
535
|
self.set_fill(opacity=factor * self.get_fill_opacity(), family=False)
|
|
@@ -587,7 +639,6 @@ class VMobject(Mobject):
|
|
|
587
639
|
:meth:`~.VMobject.set_sheen`
|
|
588
640
|
:meth:`~.VMobject.rotate_sheen_direction`
|
|
589
641
|
"""
|
|
590
|
-
|
|
591
642
|
direction = np.array(direction)
|
|
592
643
|
if family:
|
|
593
644
|
for submob in self.get_family():
|
|
@@ -653,7 +704,6 @@ class VMobject(Mobject):
|
|
|
653
704
|
circle = Circle(fill_opacity=1).set_sheen(-0.3, DR)
|
|
654
705
|
self.add(circle)
|
|
655
706
|
"""
|
|
656
|
-
|
|
657
707
|
if family:
|
|
658
708
|
for submob in self.submobjects:
|
|
659
709
|
submob.set_sheen(factor, direction, family)
|
|
@@ -709,14 +759,14 @@ class VMobject(Mobject):
|
|
|
709
759
|
submob.z_index_group = self
|
|
710
760
|
return self
|
|
711
761
|
|
|
712
|
-
def set_points(self, points:
|
|
762
|
+
def set_points(self, points: Point3DLike_Array) -> Self:
|
|
713
763
|
self.points: Point3D_Array = np.array(points)
|
|
714
764
|
return self
|
|
715
765
|
|
|
716
766
|
def resize_points(
|
|
717
767
|
self,
|
|
718
768
|
new_length: int,
|
|
719
|
-
resize_func: Callable[[
|
|
769
|
+
resize_func: Callable[[Point3D_Array, int], Point3D_Array] = resize_array,
|
|
720
770
|
) -> Self:
|
|
721
771
|
"""Resize the array of anchor points and handles to have
|
|
722
772
|
the specified size.
|
|
@@ -736,10 +786,10 @@ class VMobject(Mobject):
|
|
|
736
786
|
|
|
737
787
|
def set_anchors_and_handles(
|
|
738
788
|
self,
|
|
739
|
-
anchors1:
|
|
740
|
-
handles1:
|
|
741
|
-
handles2:
|
|
742
|
-
anchors2:
|
|
789
|
+
anchors1: Point3DLike_Array,
|
|
790
|
+
handles1: Point3DLike_Array,
|
|
791
|
+
handles2: Point3DLike_Array,
|
|
792
|
+
anchors2: Point3DLike_Array,
|
|
743
793
|
) -> Self:
|
|
744
794
|
"""Given two sets of anchors and handles, process them to set them as anchors
|
|
745
795
|
and handles of the VMobject.
|
|
@@ -757,7 +807,7 @@ class VMobject(Mobject):
|
|
|
757
807
|
assert len(anchors1) == len(handles1) == len(handles2) == len(anchors2)
|
|
758
808
|
nppcc = self.n_points_per_cubic_curve # 4
|
|
759
809
|
total_len = nppcc * len(anchors1)
|
|
760
|
-
self.points = np.
|
|
810
|
+
self.points = np.empty((total_len, self.dim))
|
|
761
811
|
# the following will, from the four sets, dispatch them in points such that
|
|
762
812
|
# self.points = [
|
|
763
813
|
# anchors1[0], handles1[0], handles2[0], anchors1[0], anchors1[1],
|
|
@@ -769,31 +819,69 @@ class VMobject(Mobject):
|
|
|
769
819
|
return self
|
|
770
820
|
|
|
771
821
|
def clear_points(self) -> None:
|
|
822
|
+
# TODO: shouldn't this return self instead of None?
|
|
772
823
|
self.points = np.zeros((0, self.dim))
|
|
773
824
|
|
|
774
|
-
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
|
+
"""
|
|
775
839
|
# TODO, check that number new points is a multiple of 4?
|
|
776
840
|
# or else that if len(self.points) % 4 == 1, then
|
|
777
841
|
# len(new_points) % 4 == 3?
|
|
778
|
-
|
|
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
|
|
779
847
|
return self
|
|
780
848
|
|
|
781
|
-
def start_new_path(self, point:
|
|
782
|
-
|
|
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:
|
|
783
870
|
# close the open path by appending the last
|
|
784
871
|
# start anchor sufficiently often
|
|
785
872
|
last_anchor = self.get_start_anchors()[-1]
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
873
|
+
closure = [last_anchor] * (nppc - (n_points % nppc))
|
|
874
|
+
self.append_points(closure + [point])
|
|
875
|
+
else:
|
|
876
|
+
self.append_points([point])
|
|
789
877
|
return self
|
|
790
878
|
|
|
791
879
|
def add_cubic_bezier_curve(
|
|
792
880
|
self,
|
|
793
|
-
anchor1:
|
|
794
|
-
handle1:
|
|
795
|
-
handle2:
|
|
796
|
-
anchor2:
|
|
881
|
+
anchor1: Point3DLike,
|
|
882
|
+
handle1: Point3DLike,
|
|
883
|
+
handle2: Point3DLike,
|
|
884
|
+
anchor2: Point3DLike,
|
|
797
885
|
) -> None:
|
|
798
886
|
# TODO, check the len(self.points) % 4 == 0?
|
|
799
887
|
self.append_points([anchor1, handle1, handle2, anchor2])
|
|
@@ -804,9 +892,9 @@ class VMobject(Mobject):
|
|
|
804
892
|
|
|
805
893
|
def add_cubic_bezier_curve_to(
|
|
806
894
|
self,
|
|
807
|
-
handle1:
|
|
808
|
-
handle2:
|
|
809
|
-
anchor:
|
|
895
|
+
handle1: Point3DLike,
|
|
896
|
+
handle2: Point3DLike,
|
|
897
|
+
anchor: Point3DLike,
|
|
810
898
|
) -> Self:
|
|
811
899
|
"""Add cubic bezier curve to the path.
|
|
812
900
|
|
|
@@ -836,8 +924,8 @@ class VMobject(Mobject):
|
|
|
836
924
|
|
|
837
925
|
def add_quadratic_bezier_curve_to(
|
|
838
926
|
self,
|
|
839
|
-
handle:
|
|
840
|
-
anchor:
|
|
927
|
+
handle: Point3DLike,
|
|
928
|
+
anchor: Point3DLike,
|
|
841
929
|
) -> Self:
|
|
842
930
|
"""Add Quadratic bezier curve to the path.
|
|
843
931
|
|
|
@@ -860,30 +948,29 @@ class VMobject(Mobject):
|
|
|
860
948
|
)
|
|
861
949
|
return self
|
|
862
950
|
|
|
863
|
-
def add_line_to(self, point:
|
|
951
|
+
def add_line_to(self, point: Point3DLike) -> Self:
|
|
864
952
|
"""Add a straight line from the last point of VMobject to the given point.
|
|
865
953
|
|
|
866
954
|
Parameters
|
|
867
955
|
----------
|
|
868
956
|
|
|
869
957
|
point
|
|
870
|
-
end of the straight line.
|
|
958
|
+
The end of the straight line.
|
|
871
959
|
|
|
872
960
|
Returns
|
|
873
961
|
-------
|
|
874
962
|
:class:`VMobject`
|
|
875
963
|
``self``
|
|
876
964
|
"""
|
|
877
|
-
nppcc = self.n_points_per_cubic_curve
|
|
878
965
|
self.add_cubic_bezier_curve_to(
|
|
879
966
|
*(
|
|
880
|
-
interpolate(self.get_last_point(), point,
|
|
881
|
-
for
|
|
967
|
+
interpolate(self.get_last_point(), point, t)
|
|
968
|
+
for t in self._bezier_t_values[1:]
|
|
882
969
|
)
|
|
883
970
|
)
|
|
884
971
|
return self
|
|
885
972
|
|
|
886
|
-
def add_smooth_curve_to(self, *points:
|
|
973
|
+
def add_smooth_curve_to(self, *points: Point3DLike) -> Self:
|
|
887
974
|
"""Creates a smooth curve from given points and add it to the VMobject. If two points are passed in, the first is interpreted
|
|
888
975
|
as a handle, the second as an anchor.
|
|
889
976
|
|
|
@@ -942,16 +1029,58 @@ class VMobject(Mobject):
|
|
|
942
1029
|
if not self.is_closed():
|
|
943
1030
|
self.add_line_to(self.get_subpaths()[-1][0])
|
|
944
1031
|
|
|
945
|
-
def add_points_as_corners(self, points:
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
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)
|
|
1073
|
+
|
|
1074
|
+
self.append_points(new_points)
|
|
1075
|
+
return self
|
|
949
1076
|
|
|
950
|
-
def set_points_as_corners(self, points:
|
|
951
|
-
"""Given an array of points, set them as
|
|
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`.
|
|
952
1080
|
|
|
953
|
-
To achieve that, this algorithm sets handles aligned with the anchors
|
|
954
|
-
|
|
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.
|
|
955
1084
|
|
|
956
1085
|
Parameters
|
|
957
1086
|
----------
|
|
@@ -961,7 +1090,7 @@ class VMobject(Mobject):
|
|
|
961
1090
|
Returns
|
|
962
1091
|
-------
|
|
963
1092
|
:class:`VMobject`
|
|
964
|
-
|
|
1093
|
+
The VMobject itself, after setting the new points as corners.
|
|
965
1094
|
|
|
966
1095
|
|
|
967
1096
|
Examples
|
|
@@ -984,16 +1113,15 @@ class VMobject(Mobject):
|
|
|
984
1113
|
vmob.set_points_as_corners(corners).scale(2)
|
|
985
1114
|
self.add(vmob)
|
|
986
1115
|
"""
|
|
987
|
-
nppcc = self.n_points_per_cubic_curve
|
|
988
1116
|
points = np.array(points)
|
|
989
1117
|
# This will set the handles aligned with the anchors.
|
|
990
1118
|
# Id est, a bezier curve will be the segment from the two anchors such that the handles belongs to this segment.
|
|
991
1119
|
self.set_anchors_and_handles(
|
|
992
|
-
*(interpolate(points[:-1], points[1:],
|
|
1120
|
+
*(interpolate(points[:-1], points[1:], t) for t in self._bezier_t_values)
|
|
993
1121
|
)
|
|
994
1122
|
return self
|
|
995
1123
|
|
|
996
|
-
def set_points_smoothly(self, points:
|
|
1124
|
+
def set_points_smoothly(self, points: Point3DLike_Array) -> Self:
|
|
997
1125
|
self.set_points_as_corners(points)
|
|
998
1126
|
self.make_smooth()
|
|
999
1127
|
return self
|
|
@@ -1019,7 +1147,7 @@ class VMobject(Mobject):
|
|
|
1019
1147
|
# The append is needed as the last element is not reached when slicing with numpy.
|
|
1020
1148
|
anchors = np.append(subpath[::nppcc], subpath[-1:], 0)
|
|
1021
1149
|
if mode == "smooth":
|
|
1022
|
-
h1, h2 =
|
|
1150
|
+
h1, h2 = get_smooth_cubic_bezier_handle_points(anchors)
|
|
1023
1151
|
else: # mode == "jagged"
|
|
1024
1152
|
# The following will make the handles aligned with the anchors, thus making the bezier curve a segment
|
|
1025
1153
|
a1 = anchors[:-1]
|
|
@@ -1038,19 +1166,17 @@ class VMobject(Mobject):
|
|
|
1038
1166
|
def make_jagged(self) -> Self:
|
|
1039
1167
|
return self.change_anchor_mode("jagged")
|
|
1040
1168
|
|
|
1041
|
-
def add_subpath(self, points:
|
|
1169
|
+
def add_subpath(self, points: CubicBezierPathLike) -> Self:
|
|
1042
1170
|
assert len(points) % 4 == 0
|
|
1043
|
-
self.
|
|
1171
|
+
self.append_points(points)
|
|
1044
1172
|
return self
|
|
1045
1173
|
|
|
1046
1174
|
def append_vectorized_mobject(self, vectorized_mobject: VMobject) -> None:
|
|
1047
|
-
new_points = list(vectorized_mobject.points)
|
|
1048
|
-
|
|
1049
1175
|
if self.has_new_path_started():
|
|
1050
1176
|
# Remove last point, which is starting
|
|
1051
1177
|
# a new path
|
|
1052
1178
|
self.points = self.points[:-1]
|
|
1053
|
-
self.append_points(
|
|
1179
|
+
self.append_points(vectorized_mobject.points)
|
|
1054
1180
|
|
|
1055
1181
|
def apply_function(self, function: MappingFunction) -> Self:
|
|
1056
1182
|
factor = self.pre_function_handle_to_anchor_scale_factor
|
|
@@ -1065,7 +1191,7 @@ class VMobject(Mobject):
|
|
|
1065
1191
|
self,
|
|
1066
1192
|
angle: float,
|
|
1067
1193
|
axis: Vector3D = OUT,
|
|
1068
|
-
about_point:
|
|
1194
|
+
about_point: Point3DLike | None = None,
|
|
1069
1195
|
**kwargs,
|
|
1070
1196
|
) -> Self:
|
|
1071
1197
|
self.rotate_sheen_direction(angle, axis)
|
|
@@ -1104,10 +1230,10 @@ class VMobject(Mobject):
|
|
|
1104
1230
|
return self
|
|
1105
1231
|
|
|
1106
1232
|
#
|
|
1107
|
-
def consider_points_equals(self, p0:
|
|
1233
|
+
def consider_points_equals(self, p0: Point3DLike, p1: Point3DLike) -> bool:
|
|
1108
1234
|
return np.allclose(p0, p1, atol=self.tolerance_for_point_equality)
|
|
1109
1235
|
|
|
1110
|
-
def consider_points_equals_2d(self, p0:
|
|
1236
|
+
def consider_points_equals_2d(self, p0: Point2DLike, p1: Point2DLike) -> bool:
|
|
1111
1237
|
"""Determine if two points are close enough to be considered equal.
|
|
1112
1238
|
|
|
1113
1239
|
This uses the algorithm from np.isclose(), but expanded here for the
|
|
@@ -1128,19 +1254,17 @@ class VMobject(Mobject):
|
|
|
1128
1254
|
atol = self.tolerance_for_point_equality
|
|
1129
1255
|
if abs(p0[0] - p1[0]) > atol + rtol * abs(p1[0]):
|
|
1130
1256
|
return False
|
|
1131
|
-
|
|
1132
|
-
return False
|
|
1133
|
-
return True
|
|
1257
|
+
return abs(p0[1] - p1[1]) <= atol + rtol * abs(p1[1])
|
|
1134
1258
|
|
|
1135
1259
|
# Information about line
|
|
1136
1260
|
def get_cubic_bezier_tuples_from_points(
|
|
1137
|
-
self, points:
|
|
1138
|
-
) ->
|
|
1261
|
+
self, points: CubicBezierPathLike
|
|
1262
|
+
) -> CubicBezierPoints_Array:
|
|
1139
1263
|
return np.array(self.gen_cubic_bezier_tuples_from_points(points))
|
|
1140
1264
|
|
|
1141
1265
|
def gen_cubic_bezier_tuples_from_points(
|
|
1142
|
-
self, points:
|
|
1143
|
-
) -> tuple[
|
|
1266
|
+
self, points: CubicBezierPathLike
|
|
1267
|
+
) -> tuple[CubicBezierPointsLike, ...]:
|
|
1144
1268
|
"""Returns the bezier tuples from an array of points.
|
|
1145
1269
|
|
|
1146
1270
|
self.points is a list of the anchors and handles of the bezier curves of the mobject (ie [anchor1, handle1, handle2, anchor2, anchor3 ..])
|
|
@@ -1164,14 +1288,14 @@ class VMobject(Mobject):
|
|
|
1164
1288
|
# Basically take every nppcc element.
|
|
1165
1289
|
return tuple(points[i : i + nppcc] for i in range(0, len(points), nppcc))
|
|
1166
1290
|
|
|
1167
|
-
def get_cubic_bezier_tuples(self) ->
|
|
1291
|
+
def get_cubic_bezier_tuples(self) -> CubicBezierPoints_Array:
|
|
1168
1292
|
return self.get_cubic_bezier_tuples_from_points(self.points)
|
|
1169
1293
|
|
|
1170
1294
|
def _gen_subpaths_from_points(
|
|
1171
1295
|
self,
|
|
1172
|
-
points:
|
|
1296
|
+
points: CubicBezierPath,
|
|
1173
1297
|
filter_func: Callable[[int], bool],
|
|
1174
|
-
) ->
|
|
1298
|
+
) -> Iterable[CubicSpline]:
|
|
1175
1299
|
"""Given an array of points defining the bezier curves of the vmobject, return subpaths formed by these points.
|
|
1176
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.
|
|
1177
1301
|
|
|
@@ -1189,7 +1313,7 @@ class VMobject(Mobject):
|
|
|
1189
1313
|
|
|
1190
1314
|
Returns
|
|
1191
1315
|
-------
|
|
1192
|
-
|
|
1316
|
+
Iterable[CubicSpline]
|
|
1193
1317
|
subpaths formed by the points.
|
|
1194
1318
|
"""
|
|
1195
1319
|
nppcc = self.n_points_per_cubic_curve
|
|
@@ -1201,7 +1325,7 @@ class VMobject(Mobject):
|
|
|
1201
1325
|
if (i2 - i1) >= nppcc
|
|
1202
1326
|
)
|
|
1203
1327
|
|
|
1204
|
-
def get_subpaths_from_points(self, points:
|
|
1328
|
+
def get_subpaths_from_points(self, points: CubicBezierPath) -> list[CubicSpline]:
|
|
1205
1329
|
return list(
|
|
1206
1330
|
self._gen_subpaths_from_points(
|
|
1207
1331
|
points,
|
|
@@ -1210,26 +1334,26 @@ class VMobject(Mobject):
|
|
|
1210
1334
|
)
|
|
1211
1335
|
|
|
1212
1336
|
def gen_subpaths_from_points_2d(
|
|
1213
|
-
self, points:
|
|
1214
|
-
) ->
|
|
1337
|
+
self, points: CubicBezierPath
|
|
1338
|
+
) -> Iterable[CubicSpline]:
|
|
1215
1339
|
return self._gen_subpaths_from_points(
|
|
1216
1340
|
points,
|
|
1217
1341
|
lambda n: not self.consider_points_equals_2d(points[n - 1], points[n]),
|
|
1218
1342
|
)
|
|
1219
1343
|
|
|
1220
|
-
def get_subpaths(self) -> list[
|
|
1344
|
+
def get_subpaths(self) -> list[CubicSpline]:
|
|
1221
1345
|
"""Returns subpaths formed by the curves of the VMobject.
|
|
1222
1346
|
|
|
1223
1347
|
Subpaths are ranges of curves with each pair of consecutive curves having their end/start points coincident.
|
|
1224
1348
|
|
|
1225
1349
|
Returns
|
|
1226
1350
|
-------
|
|
1227
|
-
list[
|
|
1351
|
+
list[CubicSpline]
|
|
1228
1352
|
subpaths.
|
|
1229
1353
|
"""
|
|
1230
1354
|
return self.get_subpaths_from_points(self.points)
|
|
1231
1355
|
|
|
1232
|
-
def get_nth_curve_points(self, n: int) ->
|
|
1356
|
+
def get_nth_curve_points(self, n: int) -> CubicBezierPoints:
|
|
1233
1357
|
"""Returns the points defining the nth curve of the vmobject.
|
|
1234
1358
|
|
|
1235
1359
|
Parameters
|
|
@@ -1239,7 +1363,7 @@ class VMobject(Mobject):
|
|
|
1239
1363
|
|
|
1240
1364
|
Returns
|
|
1241
1365
|
-------
|
|
1242
|
-
|
|
1366
|
+
CubicBezierPoints
|
|
1243
1367
|
points defining the nth bezier curve (anchors, handles)
|
|
1244
1368
|
"""
|
|
1245
1369
|
assert n < self.get_num_curves()
|
|
@@ -1308,7 +1432,6 @@ class VMobject(Mobject):
|
|
|
1308
1432
|
length : :class:`float`
|
|
1309
1433
|
The length of the nth curve.
|
|
1310
1434
|
"""
|
|
1311
|
-
|
|
1312
1435
|
_, length = self.get_nth_curve_function_with_length(n, sample_points)
|
|
1313
1436
|
|
|
1314
1437
|
return length
|
|
@@ -1334,7 +1457,6 @@ class VMobject(Mobject):
|
|
|
1334
1457
|
length : :class:`float`
|
|
1335
1458
|
The length of the nth curve.
|
|
1336
1459
|
"""
|
|
1337
|
-
|
|
1338
1460
|
curve = self.get_nth_curve_function(n)
|
|
1339
1461
|
norms = self.get_nth_curve_length_pieces(n, sample_points=sample_points)
|
|
1340
1462
|
length = np.sum(norms)
|
|
@@ -1354,15 +1476,14 @@ class VMobject(Mobject):
|
|
|
1354
1476
|
|
|
1355
1477
|
def get_curve_functions(
|
|
1356
1478
|
self,
|
|
1357
|
-
) ->
|
|
1479
|
+
) -> Iterable[Callable[[float], Point3D]]:
|
|
1358
1480
|
"""Gets the functions for the curves of the mobject.
|
|
1359
1481
|
|
|
1360
1482
|
Returns
|
|
1361
1483
|
-------
|
|
1362
|
-
|
|
1484
|
+
Iterable[Callable[[float], Point3D]]
|
|
1363
1485
|
The functions for the curves.
|
|
1364
1486
|
"""
|
|
1365
|
-
|
|
1366
1487
|
num_curves = self.get_num_curves()
|
|
1367
1488
|
|
|
1368
1489
|
for n in range(num_curves):
|
|
@@ -1370,7 +1491,7 @@ class VMobject(Mobject):
|
|
|
1370
1491
|
|
|
1371
1492
|
def get_curve_functions_with_lengths(
|
|
1372
1493
|
self, **kwargs
|
|
1373
|
-
) ->
|
|
1494
|
+
) -> Iterable[tuple[Callable[[float], Point3D], float]]:
|
|
1374
1495
|
"""Gets the functions and lengths of the curves for the mobject.
|
|
1375
1496
|
|
|
1376
1497
|
Parameters
|
|
@@ -1380,10 +1501,9 @@ class VMobject(Mobject):
|
|
|
1380
1501
|
|
|
1381
1502
|
Returns
|
|
1382
1503
|
-------
|
|
1383
|
-
|
|
1504
|
+
Iterable[tuple[Callable[[float], Point3D], float]]
|
|
1384
1505
|
The functions and lengths of the curves.
|
|
1385
1506
|
"""
|
|
1386
|
-
|
|
1387
1507
|
num_curves = self.get_num_curves()
|
|
1388
1508
|
|
|
1389
1509
|
for n in range(num_curves):
|
|
@@ -1425,7 +1545,6 @@ class VMobject(Mobject):
|
|
|
1425
1545
|
line.point_from_proportion(proportion)
|
|
1426
1546
|
))
|
|
1427
1547
|
"""
|
|
1428
|
-
|
|
1429
1548
|
if alpha < 0 or alpha > 1:
|
|
1430
1549
|
raise ValueError(f"Alpha {alpha} not between 0 and 1.")
|
|
1431
1550
|
|
|
@@ -1454,7 +1573,7 @@ class VMobject(Mobject):
|
|
|
1454
1573
|
|
|
1455
1574
|
def proportion_from_point(
|
|
1456
1575
|
self,
|
|
1457
|
-
point:
|
|
1576
|
+
point: Point3DLike,
|
|
1458
1577
|
) -> float:
|
|
1459
1578
|
"""Returns the proportion along the path of the :class:`VMobject`
|
|
1460
1579
|
a particular given point is at.
|
|
@@ -1543,7 +1662,7 @@ class VMobject(Mobject):
|
|
|
1543
1662
|
nppcc = self.n_points_per_cubic_curve
|
|
1544
1663
|
return self.points[nppcc - 1 :: nppcc]
|
|
1545
1664
|
|
|
1546
|
-
def get_anchors(self) ->
|
|
1665
|
+
def get_anchors(self) -> list[Point3D]:
|
|
1547
1666
|
"""Returns the anchors of the curves forming the VMobject.
|
|
1548
1667
|
|
|
1549
1668
|
Returns
|
|
@@ -1577,7 +1696,6 @@ class VMobject(Mobject):
|
|
|
1577
1696
|
float
|
|
1578
1697
|
The length of the :class:`VMobject`.
|
|
1579
1698
|
"""
|
|
1580
|
-
|
|
1581
1699
|
return sum(
|
|
1582
1700
|
length
|
|
1583
1701
|
for _, length in self.get_curve_functions_with_lengths(
|
|
@@ -1682,8 +1800,8 @@ class VMobject(Mobject):
|
|
|
1682
1800
|
return self
|
|
1683
1801
|
|
|
1684
1802
|
def insert_n_curves_to_point_list(
|
|
1685
|
-
self, n: int, points:
|
|
1686
|
-
) ->
|
|
1803
|
+
self, n: int, points: BezierPathLike
|
|
1804
|
+
) -> BezierPath:
|
|
1687
1805
|
"""Given an array of k points defining a bezier curves (anchors and handles), returns points defining exactly k + n bezier curves.
|
|
1688
1806
|
|
|
1689
1807
|
Parameters
|
|
@@ -1697,44 +1815,14 @@ class VMobject(Mobject):
|
|
|
1697
1815
|
-------
|
|
1698
1816
|
Points generated.
|
|
1699
1817
|
"""
|
|
1700
|
-
|
|
1701
1818
|
if len(points) == 1:
|
|
1702
1819
|
nppcc = self.n_points_per_cubic_curve
|
|
1703
1820
|
return np.repeat(points, nppcc * n, 0)
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
# it's total length is target_num. For example,
|
|
1710
|
-
# with curr_num = 10, target_num = 15, this would
|
|
1711
|
-
# be [0, 0, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9]
|
|
1712
|
-
repeat_indices = (np.arange(target_num, dtype="i") * curr_num) // target_num
|
|
1713
|
-
|
|
1714
|
-
# If the nth term of this list is k, it means
|
|
1715
|
-
# that the nth curve of our path should be split
|
|
1716
|
-
# into k pieces.
|
|
1717
|
-
# In the above example our array had the following elements
|
|
1718
|
-
# [0, 0, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9]
|
|
1719
|
-
# We have two 0s, one 1, two 2s and so on.
|
|
1720
|
-
# The split factors array would hence be:
|
|
1721
|
-
# [2, 1, 2, 1, 2, 1, 2, 1, 2, 1]
|
|
1722
|
-
split_factors = np.zeros(curr_num, dtype="i")
|
|
1723
|
-
for val in repeat_indices:
|
|
1724
|
-
split_factors[val] += 1
|
|
1725
|
-
|
|
1726
|
-
new_points = np.zeros((0, self.dim))
|
|
1727
|
-
for quad, sf in zip(bezier_quads, split_factors):
|
|
1728
|
-
# What was once a single cubic curve defined
|
|
1729
|
-
# by "quad" will now be broken into sf
|
|
1730
|
-
# smaller cubic curves
|
|
1731
|
-
alphas = np.linspace(0, 1, sf + 1)
|
|
1732
|
-
for a1, a2 in zip(alphas, alphas[1:]):
|
|
1733
|
-
new_points = np.append(
|
|
1734
|
-
new_points,
|
|
1735
|
-
partial_bezier_points(quad, a1, a2),
|
|
1736
|
-
axis=0,
|
|
1737
|
-
)
|
|
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)
|
|
1738
1826
|
return new_points
|
|
1739
1827
|
|
|
1740
1828
|
def align_rgbas(self, vmobject: VMobject) -> Self:
|
|
@@ -1750,7 +1838,7 @@ class VMobject(Mobject):
|
|
|
1750
1838
|
setattr(self, attr, new_a1)
|
|
1751
1839
|
return self
|
|
1752
1840
|
|
|
1753
|
-
def get_point_mobject(self, center:
|
|
1841
|
+
def get_point_mobject(self, center: Point3DLike | None = None) -> VectorizedPoint:
|
|
1754
1842
|
if center is None:
|
|
1755
1843
|
center = self.get_center()
|
|
1756
1844
|
point = VectorizedPoint(center)
|
|
@@ -1787,60 +1875,91 @@ class VMobject(Mobject):
|
|
|
1787
1875
|
a: float,
|
|
1788
1876
|
b: float,
|
|
1789
1877
|
) -> Self:
|
|
1790
|
-
"""Given
|
|
1791
|
-
|
|
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``.
|
|
1792
1882
|
|
|
1793
1883
|
Parameters
|
|
1794
1884
|
----------
|
|
1795
1885
|
vmobject
|
|
1796
|
-
The
|
|
1886
|
+
The :class:`.VMobject` that will serve as a model.
|
|
1797
1887
|
a
|
|
1798
|
-
|
|
1888
|
+
The lower bound for ``t``.
|
|
1799
1889
|
b
|
|
1800
|
-
|
|
1890
|
+
The upper bound for ``t``
|
|
1801
1891
|
|
|
1802
1892
|
Returns
|
|
1803
1893
|
-------
|
|
1804
|
-
:class
|
|
1805
|
-
|
|
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`.
|
|
1806
1901
|
"""
|
|
1807
|
-
|
|
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
|
+
)
|
|
1808
1907
|
# Partial curve includes three portions:
|
|
1809
|
-
# - A middle section, which matches the curve exactly
|
|
1810
|
-
# - A start, which is some ending portion of an inner cubic
|
|
1811
|
-
# - 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.
|
|
1812
1911
|
if a <= 0 and b >= 1:
|
|
1813
1912
|
self.set_points(vmobject.points)
|
|
1814
1913
|
return self
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
# The following two lines will compute which bezier curves of the given mobject need to be processed.
|
|
1819
|
-
# The residue basically indicates de proportion of the selected bezier curve that have to be selected.
|
|
1820
|
-
# 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
|
|
1821
|
-
lower_index, lower_residue = integer_interpolate(0, num_cubics, a)
|
|
1822
|
-
upper_index, upper_residue = integer_interpolate(0, num_cubics, b)
|
|
1823
|
-
|
|
1824
|
-
self.clear_points()
|
|
1825
|
-
if num_cubics == 0:
|
|
1914
|
+
num_curves = vmobject.get_num_curves()
|
|
1915
|
+
if num_curves == 0:
|
|
1916
|
+
self.clear_points()
|
|
1826
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.
|
|
1827
1932
|
if lower_index == upper_index:
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
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,
|
|
1834
1939
|
)
|
|
1835
1940
|
else:
|
|
1836
|
-
|
|
1837
|
-
|
|
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,
|
|
1838
1949
|
)
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
self.
|
|
1842
|
-
|
|
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,
|
|
1843
1961
|
)
|
|
1962
|
+
|
|
1844
1963
|
return self
|
|
1845
1964
|
|
|
1846
1965
|
def get_subcurve(self, a: float, b: float) -> Self:
|
|
@@ -1959,19 +2078,21 @@ class VGroup(VMobject, metaclass=ConvertToOpenGL):
|
|
|
1959
2078
|
>>> triangle, square = Triangle(), Square()
|
|
1960
2079
|
>>> vg.add(triangle)
|
|
1961
2080
|
VGroup(Triangle)
|
|
1962
|
-
>>> vg + square
|
|
2081
|
+
>>> vg + square # a new VGroup is constructed
|
|
1963
2082
|
VGroup(Triangle, Square)
|
|
1964
|
-
>>> vg
|
|
2083
|
+
>>> vg # not modified
|
|
1965
2084
|
VGroup(Triangle)
|
|
1966
|
-
>>> vg += square
|
|
2085
|
+
>>> vg += square
|
|
2086
|
+
>>> vg # modifies vg
|
|
1967
2087
|
VGroup(Triangle, Square)
|
|
1968
2088
|
>>> vg.remove(triangle)
|
|
1969
2089
|
VGroup(Square)
|
|
1970
|
-
>>> vg - square
|
|
2090
|
+
>>> vg - square # a new VGroup is constructed
|
|
1971
2091
|
VGroup()
|
|
1972
|
-
>>> vg
|
|
2092
|
+
>>> vg # not modified
|
|
1973
2093
|
VGroup(Square)
|
|
1974
|
-
>>> vg -= square
|
|
2094
|
+
>>> vg -= square
|
|
2095
|
+
>>> vg # modifies vg
|
|
1975
2096
|
VGroup()
|
|
1976
2097
|
|
|
1977
2098
|
.. manim:: ArcShapeIris
|
|
@@ -1991,12 +2112,14 @@ class VGroup(VMobject, metaclass=ConvertToOpenGL):
|
|
|
1991
2112
|
|
|
1992
2113
|
"""
|
|
1993
2114
|
|
|
1994
|
-
def __init__(
|
|
2115
|
+
def __init__(
|
|
2116
|
+
self, *vmobjects: VMobject | Iterable[VMobject], **kwargs: Any
|
|
2117
|
+
) -> None:
|
|
1995
2118
|
super().__init__(**kwargs)
|
|
1996
2119
|
self.add(*vmobjects)
|
|
1997
2120
|
|
|
1998
2121
|
def __repr__(self) -> str:
|
|
1999
|
-
return f
|
|
2122
|
+
return f"{self.__class__.__name__}({', '.join(str(mob) for mob in self.submobjects)})"
|
|
2000
2123
|
|
|
2001
2124
|
def __str__(self) -> str:
|
|
2002
2125
|
return (
|
|
@@ -2004,13 +2127,16 @@ class VGroup(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2004
2127
|
f"submobject{'s' if len(self.submobjects) > 0 else ''}"
|
|
2005
2128
|
)
|
|
2006
2129
|
|
|
2007
|
-
def add(
|
|
2008
|
-
|
|
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
|
|
2009
2135
|
|
|
2010
2136
|
Parameters
|
|
2011
2137
|
----------
|
|
2012
2138
|
vmobjects
|
|
2013
|
-
List of
|
|
2139
|
+
List or iterable of VMobjects to add
|
|
2014
2140
|
|
|
2015
2141
|
Returns
|
|
2016
2142
|
-------
|
|
@@ -2019,10 +2145,13 @@ class VGroup(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2019
2145
|
Raises
|
|
2020
2146
|
------
|
|
2021
2147
|
TypeError
|
|
2022
|
-
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
|
|
2023
2149
|
|
|
2024
2150
|
Examples
|
|
2025
2151
|
--------
|
|
2152
|
+
The following example shows how to add individual or multiple `VMobject` instances through the `VGroup`
|
|
2153
|
+
constructor and its `.add()` method.
|
|
2154
|
+
|
|
2026
2155
|
.. manim:: AddToVGroup
|
|
2027
2156
|
|
|
2028
2157
|
class AddToVGroup(Scene):
|
|
@@ -2051,16 +2180,65 @@ class VGroup(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2051
2180
|
self.play( # Animate group without component
|
|
2052
2181
|
(gr-circle_red).animate.shift(RIGHT)
|
|
2053
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`.
|
|
2054
2205
|
"""
|
|
2055
|
-
|
|
2056
|
-
|
|
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
|
+
):
|
|
2057
2234
|
raise TypeError(
|
|
2058
|
-
f"
|
|
2059
|
-
|
|
2060
|
-
"You can try using `Group` instead."
|
|
2235
|
+
f"{get_type_error_message(vmobject, (i, 0))} "
|
|
2236
|
+
"You can try adding this value into a Group instead."
|
|
2061
2237
|
)
|
|
2238
|
+
else:
|
|
2239
|
+
raise TypeError(get_type_error_message(vmobject, (i, 0)))
|
|
2062
2240
|
|
|
2063
|
-
return super().add(*
|
|
2241
|
+
return super().add(*valid_vmobjects)
|
|
2064
2242
|
|
|
2065
2243
|
def __add__(self, vmobject: VMobject) -> Self:
|
|
2066
2244
|
return VGroup(*self.submobjects, vmobject)
|
|
@@ -2097,8 +2275,7 @@ class VGroup(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2097
2275
|
>>> new_obj = VMobject()
|
|
2098
2276
|
>>> vgroup[0] = new_obj
|
|
2099
2277
|
"""
|
|
2100
|
-
|
|
2101
|
-
raise TypeError("All submobjects must be of type VMobject")
|
|
2278
|
+
self._assert_valid_submobjects(tuplify(value))
|
|
2102
2279
|
self.submobjects[key] = value
|
|
2103
2280
|
|
|
2104
2281
|
|
|
@@ -2242,7 +2419,7 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2242
2419
|
Normal usage::
|
|
2243
2420
|
|
|
2244
2421
|
square_obj = Square()
|
|
2245
|
-
my_dict.add([(
|
|
2422
|
+
my_dict.add([("s", square_obj)])
|
|
2246
2423
|
"""
|
|
2247
2424
|
for key, value in dict(mapping_or_iterable).items():
|
|
2248
2425
|
self.add_key_value_pair(key, value)
|
|
@@ -2269,10 +2446,10 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2269
2446
|
--------
|
|
2270
2447
|
Normal usage::
|
|
2271
2448
|
|
|
2272
|
-
my_dict.remove(
|
|
2449
|
+
my_dict.remove("square")
|
|
2273
2450
|
"""
|
|
2274
2451
|
if key not in self.submob_dict:
|
|
2275
|
-
raise KeyError("The given key '
|
|
2452
|
+
raise KeyError(f"The given key '{key!s}' is not present in the VDict")
|
|
2276
2453
|
super().remove(self.submob_dict[key])
|
|
2277
2454
|
del self.submob_dict[key]
|
|
2278
2455
|
return self
|
|
@@ -2294,7 +2471,7 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2294
2471
|
--------
|
|
2295
2472
|
Normal usage::
|
|
2296
2473
|
|
|
2297
|
-
self.play(Create(my_dict[
|
|
2474
|
+
self.play(Create(my_dict["s"]))
|
|
2298
2475
|
"""
|
|
2299
2476
|
submob = self.submob_dict[key]
|
|
2300
2477
|
return submob
|
|
@@ -2318,7 +2495,7 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2318
2495
|
Normal usage::
|
|
2319
2496
|
|
|
2320
2497
|
square_obj = Square()
|
|
2321
|
-
my_dict[
|
|
2498
|
+
my_dict["sq"] = square_obj
|
|
2322
2499
|
"""
|
|
2323
2500
|
if key in self.submob_dict:
|
|
2324
2501
|
self.remove(key)
|
|
@@ -2423,11 +2600,10 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2423
2600
|
Normal usage::
|
|
2424
2601
|
|
|
2425
2602
|
square_obj = Square()
|
|
2426
|
-
self.add_key_value_pair(
|
|
2603
|
+
self.add_key_value_pair("s", square_obj)
|
|
2427
2604
|
|
|
2428
2605
|
"""
|
|
2429
|
-
|
|
2430
|
-
raise TypeError("All submobjects must be of type VMobject")
|
|
2606
|
+
self._assert_valid_submobjects([value])
|
|
2431
2607
|
mob = value
|
|
2432
2608
|
if self.show_keys:
|
|
2433
2609
|
# This import is here and not at the top to avoid circular import
|
|
@@ -2443,7 +2619,7 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2443
2619
|
class VectorizedPoint(VMobject, metaclass=ConvertToOpenGL):
|
|
2444
2620
|
def __init__(
|
|
2445
2621
|
self,
|
|
2446
|
-
location:
|
|
2622
|
+
location: Point3DLike = ORIGIN,
|
|
2447
2623
|
color: ManimColor = BLACK,
|
|
2448
2624
|
fill_opacity: float = 0,
|
|
2449
2625
|
stroke_width: float = 0,
|
|
@@ -2643,15 +2819,12 @@ class DashedVMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
|
2643
2819
|
if vmobject.is_closed():
|
|
2644
2820
|
void_len = (1 - r) / n
|
|
2645
2821
|
else:
|
|
2646
|
-
if n == 1
|
|
2647
|
-
void_len = 1 - r
|
|
2648
|
-
else:
|
|
2649
|
-
void_len = (1 - r) / (n - 1)
|
|
2822
|
+
void_len = 1 - r if n == 1 else (1 - r) / (n - 1)
|
|
2650
2823
|
|
|
2651
2824
|
period = dash_len + void_len
|
|
2652
2825
|
phase_shift = (dash_offset % 1) * period
|
|
2653
2826
|
|
|
2654
|
-
if vmobject.is_closed():
|
|
2827
|
+
if vmobject.is_closed(): # noqa: SIM108
|
|
2655
2828
|
# closed curves have equal amount of dashes and voids
|
|
2656
2829
|
pattern_len = 1
|
|
2657
2830
|
else:
|