manim 0.17.0__py3-none-any.whl → 0.19.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- manim/__init__.py +11 -6
- manim/__main__.py +62 -19
- manim/_config/__init__.py +10 -9
- manim/_config/cli_colors.py +26 -9
- manim/_config/default.cfg +1 -3
- manim/_config/logger_utils.py +23 -13
- manim/_config/utils.py +662 -468
- manim/animation/animation.py +164 -18
- manim/animation/changing.py +34 -23
- manim/animation/composition.py +265 -67
- manim/animation/creation.py +208 -26
- manim/animation/fading.py +16 -18
- manim/animation/growing.py +35 -15
- manim/animation/indication.py +150 -76
- manim/animation/movement.py +56 -22
- manim/animation/numbers.py +64 -6
- manim/animation/rotation.py +78 -7
- manim/animation/specialized.py +6 -7
- manim/animation/speedmodifier.py +13 -10
- manim/animation/transform.py +14 -11
- manim/animation/transform_matching_parts.py +3 -4
- manim/animation/updaters/mobject_update_utils.py +152 -30
- manim/animation/updaters/update.py +10 -7
- manim/camera/camera.py +182 -118
- manim/camera/mapping_camera.py +34 -3
- manim/camera/moving_camera.py +95 -74
- manim/camera/multi_camera.py +23 -15
- manim/camera/three_d_camera.py +70 -52
- manim/cli/__init__.py +17 -0
- manim/cli/cfg/group.py +76 -44
- manim/cli/checkhealth/checks.py +192 -0
- manim/cli/checkhealth/commands.py +90 -0
- manim/cli/default_group.py +158 -25
- manim/cli/init/commands.py +33 -25
- manim/cli/plugins/commands.py +16 -3
- manim/cli/render/commands.py +72 -60
- manim/cli/render/ease_of_access_options.py +4 -3
- manim/cli/render/global_options.py +59 -17
- manim/cli/render/output_options.py +6 -5
- manim/cli/render/render_options.py +98 -33
- manim/constants.py +109 -59
- manim/data_structures.py +31 -0
- manim/mobject/frame.py +8 -5
- manim/mobject/geometry/__init__.py +1 -0
- manim/mobject/geometry/arc.py +277 -135
- manim/mobject/geometry/boolean_ops.py +32 -31
- manim/mobject/geometry/labeled.py +376 -0
- manim/mobject/geometry/line.py +192 -87
- manim/mobject/geometry/polygram.py +224 -58
- manim/mobject/geometry/shape_matchers.py +61 -25
- manim/mobject/geometry/tips.py +122 -48
- manim/mobject/graph.py +1027 -419
- manim/mobject/graphing/coordinate_systems.py +533 -278
- manim/mobject/graphing/functions.py +53 -32
- manim/mobject/graphing/number_line.py +123 -65
- manim/mobject/graphing/probability.py +88 -62
- manim/mobject/graphing/scale.py +33 -19
- manim/mobject/logo.py +118 -28
- manim/mobject/matrix.py +87 -83
- manim/mobject/mobject.py +912 -442
- manim/mobject/opengl/dot_cloud.py +16 -5
- manim/mobject/opengl/opengl_compatibility.py +4 -2
- manim/mobject/opengl/opengl_geometry.py +254 -153
- manim/mobject/opengl/opengl_image_mobject.py +3 -1
- manim/mobject/opengl/opengl_mobject.py +779 -482
- manim/mobject/opengl/opengl_point_cloud_mobject.py +41 -14
- manim/mobject/opengl/opengl_surface.py +14 -92
- manim/mobject/opengl/opengl_three_dimensions.py +12 -8
- manim/mobject/opengl/opengl_vectorized_mobject.py +98 -100
- manim/mobject/svg/brace.py +173 -41
- manim/mobject/svg/svg_mobject.py +139 -53
- manim/mobject/table.py +61 -68
- manim/mobject/text/code_mobject.py +193 -539
- manim/mobject/text/numbers.py +81 -34
- manim/mobject/text/tex_mobject.py +130 -78
- manim/mobject/text/text_mobject.py +288 -164
- manim/mobject/three_d/polyhedra.py +111 -13
- manim/mobject/three_d/three_d_utils.py +17 -8
- manim/mobject/three_d/three_dimensions.py +239 -106
- manim/mobject/types/image_mobject.py +50 -30
- manim/mobject/types/point_cloud_mobject.py +120 -75
- manim/mobject/types/vectorized_mobject.py +841 -408
- manim/mobject/value_tracker.py +105 -38
- manim/mobject/vector_field.py +50 -31
- manim/opengl/__init__.py +3 -3
- manim/plugins/__init__.py +14 -1
- manim/plugins/plugins_flags.py +10 -14
- manim/renderer/cairo_renderer.py +65 -50
- manim/renderer/opengl_renderer.py +89 -69
- manim/renderer/opengl_renderer_window.py +39 -18
- manim/renderer/shader.py +123 -87
- manim/renderer/shader_wrapper.py +44 -28
- manim/renderer/vectorized_mobject_rendering.py +38 -10
- manim/scene/moving_camera_scene.py +32 -3
- manim/scene/scene.py +507 -242
- manim/scene/scene_file_writer.py +371 -220
- manim/scene/section.py +20 -16
- manim/scene/three_d_scene.py +14 -22
- manim/scene/vector_space_scene.py +223 -129
- manim/scene/zoomed_scene.py +46 -41
- manim/typing.py +990 -0
- manim/utils/bezier.py +1823 -371
- manim/utils/caching.py +12 -5
- manim/utils/color/AS2700.py +236 -0
- manim/utils/color/BS381.py +318 -0
- manim/utils/color/DVIPSNAMES.py +96 -0
- manim/utils/color/SVGNAMES.py +179 -0
- manim/utils/color/X11.py +533 -0
- manim/utils/color/XKCD.py +952 -0
- manim/utils/color/__init__.py +61 -0
- manim/utils/color/core.py +1667 -0
- manim/utils/color/manim_colors.py +218 -0
- manim/utils/commands.py +48 -20
- manim/utils/config_ops.py +39 -19
- manim/utils/debug.py +8 -7
- manim/utils/deprecation.py +86 -39
- manim/utils/docbuild/__init__.py +17 -0
- manim/utils/docbuild/autoaliasattr_directive.py +236 -0
- manim/utils/docbuild/autocolor_directive.py +99 -0
- manim/utils/docbuild/manim_directive.py +94 -41
- manim/utils/docbuild/module_parsing.py +245 -0
- manim/utils/exceptions.py +6 -0
- manim/utils/family.py +5 -3
- manim/utils/family_ops.py +17 -4
- manim/utils/file_ops.py +27 -17
- manim/utils/hashing.py +55 -45
- manim/utils/images.py +13 -7
- manim/utils/ipython_magic.py +13 -7
- manim/utils/iterables.py +163 -120
- manim/utils/module_ops.py +66 -24
- manim/utils/opengl.py +77 -24
- manim/utils/parameter_parsing.py +32 -0
- manim/utils/paths.py +30 -33
- manim/utils/polylabel.py +235 -0
- manim/utils/qhull.py +218 -0
- manim/utils/rate_functions.py +98 -32
- manim/utils/simple_functions.py +25 -33
- manim/utils/sounds.py +7 -1
- manim/utils/space_ops.py +188 -115
- manim/utils/testing/__init__.py +17 -0
- manim/utils/testing/_frames_testers.py +13 -8
- manim/utils/testing/_show_diff.py +5 -3
- manim/utils/testing/_test_class_makers.py +34 -18
- manim/utils/testing/frames_comparison.py +37 -19
- manim/utils/tex.py +130 -198
- manim/utils/tex_file_writing.py +77 -47
- manim/utils/tex_templates.py +2 -1
- manim/utils/unit.py +6 -5
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/METADATA +64 -65
- manim-0.19.1.dist-info/RECORD +220 -0
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/WHEEL +1 -1
- manim-0.19.1.dist-info/entry_points.txt +3 -0
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE.community +1 -1
- manim/cli/new/group.py +0 -189
- manim/communitycolors.py +0 -9
- manim/gui/__init__.py +0 -0
- manim/gui/gui.py +0 -82
- manim/plugins/import_plugins.py +0 -43
- manim/utils/color.py +0 -552
- manim-0.17.0.dist-info/RECORD +0 -206
- manim-0.17.0.dist-info/entry_points.txt +0 -4
- /manim/cli/{new → checkhealth}/__init__.py +0 -0
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE +0 -0
manim/mobject/svg/brace.py
CHANGED
|
@@ -4,18 +4,21 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
__all__ = ["Brace", "BraceLabel", "ArcBrace", "BraceText", "BraceBetweenPoints"]
|
|
6
6
|
|
|
7
|
-
from typing import
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
8
|
|
|
9
9
|
import numpy as np
|
|
10
10
|
import svgelements as se
|
|
11
|
+
from typing_extensions import Self
|
|
11
12
|
|
|
12
13
|
from manim._config import config
|
|
13
14
|
from manim.mobject.geometry.arc import Arc
|
|
14
15
|
from manim.mobject.geometry.line import Line
|
|
15
16
|
from manim.mobject.mobject import Mobject
|
|
16
17
|
from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL
|
|
17
|
-
from manim.mobject.text.tex_mobject import MathTex, Tex
|
|
18
|
+
from manim.mobject.text.tex_mobject import MathTex, SingleStringMathTex, Tex
|
|
19
|
+
from manim.mobject.text.text_mobject import Text
|
|
18
20
|
|
|
21
|
+
from ...animation.animation import Animation
|
|
19
22
|
from ...animation.composition import AnimationGroup
|
|
20
23
|
from ...animation.fading import FadeIn
|
|
21
24
|
from ...animation.growing import GrowFromCenter
|
|
@@ -24,6 +27,10 @@ from ...mobject.types.vectorized_mobject import VMobject
|
|
|
24
27
|
from ...utils.color import BLACK
|
|
25
28
|
from ..svg.svg_mobject import VMobjectFromSVGPath
|
|
26
29
|
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from manim.typing import Point3D, Point3DLike, Vector3D, Vector3DLike
|
|
32
|
+
from manim.utils.color.core import ParsableManimColor
|
|
33
|
+
|
|
27
34
|
|
|
28
35
|
class Brace(VMobjectFromSVGPath):
|
|
29
36
|
"""Takes a mobject and draws a brace adjacent to it.
|
|
@@ -63,14 +70,14 @@ class Brace(VMobjectFromSVGPath):
|
|
|
63
70
|
def __init__(
|
|
64
71
|
self,
|
|
65
72
|
mobject: Mobject,
|
|
66
|
-
direction:
|
|
67
|
-
buff=0.2,
|
|
68
|
-
sharpness=2,
|
|
69
|
-
stroke_width=0,
|
|
70
|
-
fill_opacity=1.0,
|
|
71
|
-
background_stroke_width=0,
|
|
72
|
-
background_stroke_color=BLACK,
|
|
73
|
-
**kwargs,
|
|
73
|
+
direction: Vector3DLike = DOWN,
|
|
74
|
+
buff: float = 0.2,
|
|
75
|
+
sharpness: float = 2,
|
|
76
|
+
stroke_width: float = 0,
|
|
77
|
+
fill_opacity: float = 1.0,
|
|
78
|
+
background_stroke_width: float = 0,
|
|
79
|
+
background_stroke_color: ParsableManimColor = BLACK,
|
|
80
|
+
**kwargs: Any,
|
|
74
81
|
):
|
|
75
82
|
path_string_template = (
|
|
76
83
|
"m0.01216 0c-0.01152 0-0.01216 6.103e-4 -0.01216 0.01311v0.007762c0.06776 "
|
|
@@ -123,7 +130,20 @@ class Brace(VMobjectFromSVGPath):
|
|
|
123
130
|
for mob in mobject, self:
|
|
124
131
|
mob.rotate(angle, about_point=ORIGIN)
|
|
125
132
|
|
|
126
|
-
def put_at_tip(self, mob, use_next_to=True, **kwargs):
|
|
133
|
+
def put_at_tip(self, mob: Mobject, use_next_to: bool = True, **kwargs: Any) -> Self:
|
|
134
|
+
"""Puts the given mobject at the brace tip.
|
|
135
|
+
|
|
136
|
+
Parameters
|
|
137
|
+
----------
|
|
138
|
+
mob
|
|
139
|
+
The mobject to be placed at the tip.
|
|
140
|
+
use_next_to
|
|
141
|
+
If true, then :meth:`next_to` is used to place the mobject at the
|
|
142
|
+
tip.
|
|
143
|
+
kwargs
|
|
144
|
+
Any additional keyword arguments are passed to :meth:`next_to` which
|
|
145
|
+
is used to put the mobject next to the brace tip.
|
|
146
|
+
"""
|
|
127
147
|
if use_next_to:
|
|
128
148
|
mob.next_to(self.get_tip(), np.round(self.get_direction()), **kwargs)
|
|
129
149
|
else:
|
|
@@ -133,80 +153,190 @@ class Brace(VMobjectFromSVGPath):
|
|
|
133
153
|
mob.shift(self.get_direction() * shift_distance)
|
|
134
154
|
return self
|
|
135
155
|
|
|
136
|
-
def get_text(self, *text, **kwargs):
|
|
156
|
+
def get_text(self, *text: str, **kwargs: Any) -> Tex:
|
|
157
|
+
"""Places the text at the brace tip.
|
|
158
|
+
|
|
159
|
+
Parameters
|
|
160
|
+
----------
|
|
161
|
+
text
|
|
162
|
+
The text to be placed at the brace tip.
|
|
163
|
+
kwargs
|
|
164
|
+
Any additional keyword arguments are passed to :meth:`.put_at_tip` which
|
|
165
|
+
is used to position the text at the brace tip.
|
|
166
|
+
|
|
167
|
+
Returns
|
|
168
|
+
-------
|
|
169
|
+
:class:`~.Tex`
|
|
170
|
+
"""
|
|
137
171
|
text_mob = Tex(*text)
|
|
138
172
|
self.put_at_tip(text_mob, **kwargs)
|
|
139
173
|
return text_mob
|
|
140
174
|
|
|
141
|
-
def get_tex(self, *tex, **kwargs):
|
|
175
|
+
def get_tex(self, *tex: str, **kwargs: Any) -> MathTex:
|
|
176
|
+
"""Places the tex at the brace tip.
|
|
177
|
+
|
|
178
|
+
Parameters
|
|
179
|
+
----------
|
|
180
|
+
tex
|
|
181
|
+
The tex to be placed at the brace tip.
|
|
182
|
+
kwargs
|
|
183
|
+
Any further keyword arguments are passed to :meth:`.put_at_tip` which
|
|
184
|
+
is used to position the tex at the brace tip.
|
|
185
|
+
|
|
186
|
+
Returns
|
|
187
|
+
-------
|
|
188
|
+
:class:`~.MathTex`
|
|
189
|
+
"""
|
|
142
190
|
tex_mob = MathTex(*tex)
|
|
143
191
|
self.put_at_tip(tex_mob, **kwargs)
|
|
144
192
|
return tex_mob
|
|
145
193
|
|
|
146
|
-
def get_tip(self):
|
|
194
|
+
def get_tip(self) -> Point3D:
|
|
195
|
+
"""Returns the point at the brace tip."""
|
|
147
196
|
# Returns the position of the seventh point in the path, which is the tip.
|
|
148
197
|
if config["renderer"] == "opengl":
|
|
149
198
|
return self.points[34]
|
|
150
199
|
|
|
151
200
|
return self.points[28] # = 7*4
|
|
152
201
|
|
|
153
|
-
def get_direction(self):
|
|
202
|
+
def get_direction(self) -> Vector3D:
|
|
203
|
+
"""Returns the direction from the center to the brace tip."""
|
|
154
204
|
vect = self.get_tip() - self.get_center()
|
|
155
205
|
return vect / np.linalg.norm(vect)
|
|
156
206
|
|
|
157
207
|
|
|
158
208
|
class BraceLabel(VMobject, metaclass=ConvertToOpenGL):
|
|
209
|
+
"""Create a brace with a label attached.
|
|
210
|
+
|
|
211
|
+
Parameters
|
|
212
|
+
----------
|
|
213
|
+
obj
|
|
214
|
+
The mobject adjacent to which the brace is placed.
|
|
215
|
+
text
|
|
216
|
+
The label text.
|
|
217
|
+
brace_direction
|
|
218
|
+
The direction of the brace. By default ``DOWN``.
|
|
219
|
+
label_constructor
|
|
220
|
+
A class or function used to construct a mobject representing
|
|
221
|
+
the label. By default :class:`~.MathTex`.
|
|
222
|
+
font_size
|
|
223
|
+
The font size of the label, passed to the ``label_constructor``.
|
|
224
|
+
buff
|
|
225
|
+
The buffer between the mobject and the brace.
|
|
226
|
+
brace_config
|
|
227
|
+
Arguments to be passed to :class:`.Brace`.
|
|
228
|
+
kwargs
|
|
229
|
+
Additional arguments to be passed to :class:`~.VMobject`.
|
|
230
|
+
"""
|
|
231
|
+
|
|
159
232
|
def __init__(
|
|
160
233
|
self,
|
|
161
|
-
obj,
|
|
162
|
-
text,
|
|
163
|
-
brace_direction=DOWN,
|
|
164
|
-
label_constructor=MathTex,
|
|
165
|
-
font_size=DEFAULT_FONT_SIZE,
|
|
166
|
-
buff=0.2,
|
|
167
|
-
|
|
234
|
+
obj: Mobject,
|
|
235
|
+
text: str,
|
|
236
|
+
brace_direction: Vector3DLike = DOWN,
|
|
237
|
+
label_constructor: type[SingleStringMathTex | Text] = MathTex,
|
|
238
|
+
font_size: float = DEFAULT_FONT_SIZE,
|
|
239
|
+
buff: float = 0.2,
|
|
240
|
+
brace_config: dict[str, Any] | None = None,
|
|
241
|
+
**kwargs: Any,
|
|
168
242
|
):
|
|
169
243
|
self.label_constructor = label_constructor
|
|
170
244
|
super().__init__(**kwargs)
|
|
171
245
|
|
|
172
246
|
self.brace_direction = brace_direction
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
self.brace = Brace(obj, brace_direction, buff, **kwargs)
|
|
247
|
+
if brace_config is None:
|
|
248
|
+
brace_config = {}
|
|
249
|
+
self.brace = Brace(obj, brace_direction, buff, **brace_config)
|
|
177
250
|
|
|
178
251
|
if isinstance(text, (tuple, list)):
|
|
179
|
-
self.label = self.label_constructor(
|
|
252
|
+
self.label: VMobject = self.label_constructor(
|
|
253
|
+
*text, font_size=font_size, **kwargs
|
|
254
|
+
)
|
|
180
255
|
else:
|
|
181
256
|
self.label = self.label_constructor(str(text), font_size=font_size)
|
|
182
257
|
|
|
183
258
|
self.brace.put_at_tip(self.label)
|
|
184
259
|
self.add(self.brace, self.label)
|
|
185
260
|
|
|
186
|
-
def creation_anim(
|
|
261
|
+
def creation_anim(
|
|
262
|
+
self,
|
|
263
|
+
label_anim: type[Animation] = FadeIn,
|
|
264
|
+
brace_anim: type[Animation] = GrowFromCenter,
|
|
265
|
+
) -> AnimationGroup:
|
|
187
266
|
return AnimationGroup(brace_anim(self.brace), label_anim(self.label))
|
|
188
267
|
|
|
189
|
-
def shift_brace(self, obj, **kwargs):
|
|
268
|
+
def shift_brace(self, obj: Mobject, **kwargs: Any) -> Self:
|
|
190
269
|
if isinstance(obj, list):
|
|
191
270
|
obj = self.get_group_class()(*obj)
|
|
192
271
|
self.brace = Brace(obj, self.brace_direction, **kwargs)
|
|
193
272
|
self.brace.put_at_tip(self.label)
|
|
194
273
|
return self
|
|
195
274
|
|
|
196
|
-
def change_label(self, *text, **kwargs):
|
|
197
|
-
self.
|
|
198
|
-
|
|
275
|
+
def change_label(self, *text: str, **kwargs: Any) -> Self:
|
|
276
|
+
self.remove(self.label)
|
|
277
|
+
self.label = self.label_constructor(*text, **kwargs) # type: ignore[arg-type]
|
|
199
278
|
self.brace.put_at_tip(self.label)
|
|
279
|
+
self.add(self.label)
|
|
200
280
|
return self
|
|
201
281
|
|
|
202
|
-
def change_brace_label(self, obj, *text, **kwargs):
|
|
282
|
+
def change_brace_label(self, obj: Mobject, *text: str, **kwargs: Any) -> Self:
|
|
203
283
|
self.shift_brace(obj)
|
|
204
284
|
self.change_label(*text, **kwargs)
|
|
205
285
|
return self
|
|
206
286
|
|
|
207
287
|
|
|
208
288
|
class BraceText(BraceLabel):
|
|
209
|
-
|
|
289
|
+
"""Create a brace with a text label attached.
|
|
290
|
+
|
|
291
|
+
Parameters
|
|
292
|
+
----------
|
|
293
|
+
obj
|
|
294
|
+
The mobject adjacent to which the brace is placed.
|
|
295
|
+
text
|
|
296
|
+
The label text.
|
|
297
|
+
brace_direction
|
|
298
|
+
The direction of the brace. By default ``DOWN``.
|
|
299
|
+
label_constructor
|
|
300
|
+
A class or function used to construct a mobject representing
|
|
301
|
+
the label. By default :class:`~.Text`.
|
|
302
|
+
font_size
|
|
303
|
+
The font size of the label, passed to the ``label_constructor``.
|
|
304
|
+
buff
|
|
305
|
+
The buffer between the mobject and the brace.
|
|
306
|
+
brace_config
|
|
307
|
+
Arguments to be passed to :class:`.Brace`.
|
|
308
|
+
kwargs
|
|
309
|
+
Additional arguments to be passed to :class:`~.VMobject`.
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
Examples
|
|
313
|
+
--------
|
|
314
|
+
.. manim:: BraceTextExample
|
|
315
|
+
:save_last_frame:
|
|
316
|
+
|
|
317
|
+
class BraceTextExample(Scene):
|
|
318
|
+
def construct(self):
|
|
319
|
+
s1 = Square().move_to(2*LEFT)
|
|
320
|
+
self.add(s1)
|
|
321
|
+
br1 = BraceText(s1, "Label")
|
|
322
|
+
self.add(br1)
|
|
323
|
+
|
|
324
|
+
s2 = Square().move_to(2*RIGHT)
|
|
325
|
+
self.add(s2)
|
|
326
|
+
br2 = BraceText(s2, "Label")
|
|
327
|
+
|
|
328
|
+
br2.change_label("new")
|
|
329
|
+
self.add(br2)
|
|
330
|
+
self.wait(0.1)
|
|
331
|
+
"""
|
|
332
|
+
|
|
333
|
+
def __init__(
|
|
334
|
+
self,
|
|
335
|
+
obj: Mobject,
|
|
336
|
+
text: str,
|
|
337
|
+
label_constructor: type[SingleStringMathTex | Text] = Text,
|
|
338
|
+
**kwargs: Any,
|
|
339
|
+
):
|
|
210
340
|
super().__init__(obj, text, label_constructor=label_constructor, **kwargs)
|
|
211
341
|
|
|
212
342
|
|
|
@@ -244,10 +374,10 @@ class BraceBetweenPoints(Brace):
|
|
|
244
374
|
|
|
245
375
|
def __init__(
|
|
246
376
|
self,
|
|
247
|
-
point_1:
|
|
248
|
-
point_2:
|
|
249
|
-
direction:
|
|
250
|
-
**kwargs,
|
|
377
|
+
point_1: Point3DLike,
|
|
378
|
+
point_2: Point3DLike,
|
|
379
|
+
direction: Vector3DLike = ORIGIN,
|
|
380
|
+
**kwargs: Any,
|
|
251
381
|
):
|
|
252
382
|
if all(direction == ORIGIN):
|
|
253
383
|
line_vector = np.array(point_2) - np.array(point_1)
|
|
@@ -312,10 +442,12 @@ class ArcBrace(Brace):
|
|
|
312
442
|
|
|
313
443
|
def __init__(
|
|
314
444
|
self,
|
|
315
|
-
arc: Arc
|
|
316
|
-
direction:
|
|
317
|
-
**kwargs,
|
|
445
|
+
arc: Arc | None = None,
|
|
446
|
+
direction: Vector3DLike = RIGHT,
|
|
447
|
+
**kwargs: Any,
|
|
318
448
|
):
|
|
449
|
+
if arc is None:
|
|
450
|
+
arc = Arc(start_angle=-1, angle=2, radius=1)
|
|
319
451
|
arc_end_angle = arc.start_angle + arc.angle
|
|
320
452
|
line = Line(UP * arc.start_angle, UP * arc_end_angle)
|
|
321
453
|
scale_shift = RIGHT * np.log(arc.radius)
|
manim/mobject/svg/svg_mobject.py
CHANGED
|
@@ -4,14 +4,17 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
6
|
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
7
8
|
from xml.etree import ElementTree as ET
|
|
8
9
|
|
|
9
10
|
import numpy as np
|
|
10
11
|
import svgelements as se
|
|
11
12
|
|
|
12
13
|
from manim import config, logger
|
|
14
|
+
from manim.utils.color import ManimColor, ParsableManimColor
|
|
13
15
|
|
|
14
16
|
from ...constants import RIGHT
|
|
17
|
+
from ...utils.bezier import get_quadratic_approximation_of_cubic
|
|
15
18
|
from ...utils.images import get_full_vector_image_path
|
|
16
19
|
from ...utils.iterables import hash_obj
|
|
17
20
|
from ..geometry.arc import Circle
|
|
@@ -81,6 +84,12 @@ class SVGMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
|
81
84
|
A dictionary with keyword arguments passed to
|
|
82
85
|
:class:`.VMobjectFromSVGPath` used for importing path elements.
|
|
83
86
|
If ``None`` (the default), no additional arguments are passed.
|
|
87
|
+
use_svg_cache
|
|
88
|
+
If True (default), the svg inputs (e.g. file_name, settings)
|
|
89
|
+
will be used as a key and a copy of the created mobject will
|
|
90
|
+
be saved using that key to be quickly retrieved if the same
|
|
91
|
+
inputs need be processed later. For large SVGs which are used
|
|
92
|
+
only once, this can be omitted to improve performance.
|
|
84
93
|
kwargs
|
|
85
94
|
Further arguments passed to the parent class.
|
|
86
95
|
"""
|
|
@@ -91,16 +100,17 @@ class SVGMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
|
91
100
|
should_center: bool = True,
|
|
92
101
|
height: float | None = 2,
|
|
93
102
|
width: float | None = None,
|
|
94
|
-
color:
|
|
103
|
+
color: ParsableManimColor | None = None,
|
|
95
104
|
opacity: float | None = None,
|
|
96
|
-
fill_color:
|
|
105
|
+
fill_color: ParsableManimColor | None = None,
|
|
97
106
|
fill_opacity: float | None = None,
|
|
98
|
-
stroke_color:
|
|
107
|
+
stroke_color: ParsableManimColor | None = None,
|
|
99
108
|
stroke_opacity: float | None = None,
|
|
100
109
|
stroke_width: float | None = None,
|
|
101
110
|
svg_default: dict | None = None,
|
|
102
111
|
path_string_config: dict | None = None,
|
|
103
|
-
|
|
112
|
+
use_svg_cache: bool = True,
|
|
113
|
+
**kwargs: Any,
|
|
104
114
|
):
|
|
105
115
|
super().__init__(color=None, stroke_color=None, fill_color=None, **kwargs)
|
|
106
116
|
|
|
@@ -110,13 +120,15 @@ class SVGMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
|
110
120
|
self.should_center = should_center
|
|
111
121
|
self.svg_height = height
|
|
112
122
|
self.svg_width = width
|
|
113
|
-
self.color = color
|
|
123
|
+
self.color = ManimColor(color)
|
|
114
124
|
self.opacity = opacity
|
|
115
125
|
self.fill_color = fill_color
|
|
116
|
-
self.fill_opacity = fill_opacity
|
|
126
|
+
self.fill_opacity = fill_opacity # type: ignore[assignment]
|
|
117
127
|
self.stroke_color = stroke_color
|
|
118
|
-
self.stroke_opacity = stroke_opacity
|
|
119
|
-
self.stroke_width = stroke_width
|
|
128
|
+
self.stroke_opacity = stroke_opacity # type: ignore[assignment]
|
|
129
|
+
self.stroke_width = stroke_width # type: ignore[assignment]
|
|
130
|
+
if self.stroke_width is None:
|
|
131
|
+
self.stroke_width = 0
|
|
120
132
|
|
|
121
133
|
if svg_default is None:
|
|
122
134
|
svg_default = {
|
|
@@ -134,7 +146,7 @@ class SVGMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
|
134
146
|
path_string_config = {}
|
|
135
147
|
self.path_string_config = path_string_config
|
|
136
148
|
|
|
137
|
-
self.init_svg_mobject()
|
|
149
|
+
self.init_svg_mobject(use_svg_cache=use_svg_cache)
|
|
138
150
|
|
|
139
151
|
self.set_style(
|
|
140
152
|
fill_color=fill_color,
|
|
@@ -145,7 +157,7 @@ class SVGMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
|
145
157
|
)
|
|
146
158
|
self.move_into_position()
|
|
147
159
|
|
|
148
|
-
def init_svg_mobject(self) -> None:
|
|
160
|
+
def init_svg_mobject(self, use_svg_cache: bool) -> None:
|
|
149
161
|
"""Checks whether the SVG has already been imported and
|
|
150
162
|
generates it if not.
|
|
151
163
|
|
|
@@ -153,14 +165,16 @@ class SVGMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
|
153
165
|
--------
|
|
154
166
|
:meth:`.SVGMobject.generate_mobject`
|
|
155
167
|
"""
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
168
|
+
if use_svg_cache:
|
|
169
|
+
hash_val = hash_obj(self.hash_seed)
|
|
170
|
+
if hash_val in SVG_HASH_TO_MOB_MAP:
|
|
171
|
+
mob = SVG_HASH_TO_MOB_MAP[hash_val].copy()
|
|
172
|
+
self.add(*mob)
|
|
173
|
+
return
|
|
161
174
|
|
|
162
175
|
self.generate_mobject()
|
|
163
|
-
|
|
176
|
+
if use_svg_cache:
|
|
177
|
+
SVG_HASH_TO_MOB_MAP[hash_val] = self.copy()
|
|
164
178
|
|
|
165
179
|
@property
|
|
166
180
|
def hash_seed(self) -> tuple:
|
|
@@ -181,7 +195,7 @@ class SVGMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
|
181
195
|
"""Parse the SVG and translate its elements to submobjects."""
|
|
182
196
|
file_path = self.get_file_path()
|
|
183
197
|
element_tree = ET.parse(file_path)
|
|
184
|
-
new_tree = self.modify_xml_tree(element_tree)
|
|
198
|
+
new_tree = self.modify_xml_tree(element_tree) # type: ignore[arg-type]
|
|
185
199
|
# Create a temporary svg file to dump modified svg to be parsed
|
|
186
200
|
modified_file_path = file_path.with_name(f"{file_path.stem}_{file_path.suffix}")
|
|
187
201
|
new_tree.write(modified_file_path)
|
|
@@ -218,12 +232,12 @@ class SVGMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
|
218
232
|
"style",
|
|
219
233
|
)
|
|
220
234
|
root = element_tree.getroot()
|
|
221
|
-
root_style_dict = {k: v for k, v in root.attrib.items() if k in style_keys}
|
|
235
|
+
root_style_dict = {k: v for k, v in root.attrib.items() if k in style_keys} # type: ignore[union-attr]
|
|
222
236
|
|
|
223
237
|
new_root = ET.Element("svg", {})
|
|
224
238
|
config_style_node = ET.SubElement(new_root, "g", config_style_dict)
|
|
225
239
|
root_style_node = ET.SubElement(config_style_node, "g", root_style_dict)
|
|
226
|
-
root_style_node.extend(root)
|
|
240
|
+
root_style_node.extend(root) # type: ignore[arg-type]
|
|
227
241
|
return ET.ElementTree(new_root)
|
|
228
242
|
|
|
229
243
|
def generate_config_style_dict(self) -> dict[str, str]:
|
|
@@ -252,12 +266,13 @@ class SVGMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
|
252
266
|
svg
|
|
253
267
|
The parsed SVG file.
|
|
254
268
|
"""
|
|
255
|
-
result = []
|
|
269
|
+
result: list[VMobject] = []
|
|
256
270
|
for shape in svg.elements():
|
|
257
|
-
|
|
271
|
+
# can we combine the two continue cases into one?
|
|
272
|
+
if isinstance(shape, se.Group): # noqa: SIM114
|
|
258
273
|
continue
|
|
259
274
|
elif isinstance(shape, se.Path):
|
|
260
|
-
mob = self.path_to_mobject(shape)
|
|
275
|
+
mob: VMobject = self.path_to_mobject(shape)
|
|
261
276
|
elif isinstance(shape, se.SimpleLine):
|
|
262
277
|
mob = self.line_to_mobject(shape)
|
|
263
278
|
elif isinstance(shape, se.Rect):
|
|
@@ -270,7 +285,7 @@ class SVGMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
|
270
285
|
mob = self.polyline_to_mobject(shape)
|
|
271
286
|
elif isinstance(shape, se.Text):
|
|
272
287
|
mob = self.text_to_mobject(shape)
|
|
273
|
-
elif isinstance(shape, se.Use) or type(shape)
|
|
288
|
+
elif isinstance(shape, se.Use) or type(shape) is se.SVGElement:
|
|
274
289
|
continue
|
|
275
290
|
else:
|
|
276
291
|
logger.warning(f"Unsupported element type: {type(shape)}")
|
|
@@ -411,7 +426,7 @@ class SVGMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
|
411
426
|
return vmobject_class().set_points_as_corners(points)
|
|
412
427
|
|
|
413
428
|
@staticmethod
|
|
414
|
-
def text_to_mobject(text: se.Text):
|
|
429
|
+
def text_to_mobject(text: se.Text) -> VMobject:
|
|
415
430
|
"""Convert a text element to a vectorized mobject.
|
|
416
431
|
|
|
417
432
|
.. warning::
|
|
@@ -424,16 +439,16 @@ class SVGMobject(VMobject, metaclass=ConvertToOpenGL):
|
|
|
424
439
|
The parsed SVG text.
|
|
425
440
|
"""
|
|
426
441
|
logger.warning(f"Unsupported element type: {type(text)}")
|
|
427
|
-
return
|
|
442
|
+
return # type: ignore[return-value]
|
|
428
443
|
|
|
429
444
|
def move_into_position(self) -> None:
|
|
430
445
|
"""Scale and move the generated mobject into position."""
|
|
431
446
|
if self.should_center:
|
|
432
447
|
self.center()
|
|
433
448
|
if self.svg_height is not None:
|
|
434
|
-
self.
|
|
449
|
+
self.set(height=self.svg_height)
|
|
435
450
|
if self.svg_width is not None:
|
|
436
|
-
self.
|
|
451
|
+
self.set(width=self.svg_width)
|
|
437
452
|
|
|
438
453
|
|
|
439
454
|
class VMobjectFromSVGPath(VMobject, metaclass=ConvertToOpenGL):
|
|
@@ -469,7 +484,7 @@ class VMobjectFromSVGPath(VMobject, metaclass=ConvertToOpenGL):
|
|
|
469
484
|
long_lines: bool = False,
|
|
470
485
|
should_subdivide_sharp_curves: bool = False,
|
|
471
486
|
should_remove_null_curves: bool = False,
|
|
472
|
-
**kwargs,
|
|
487
|
+
**kwargs: Any,
|
|
473
488
|
):
|
|
474
489
|
# Get rid of arcs
|
|
475
490
|
path_obj.approximate_arcs_with_quads()
|
|
@@ -481,7 +496,7 @@ class VMobjectFromSVGPath(VMobject, metaclass=ConvertToOpenGL):
|
|
|
481
496
|
|
|
482
497
|
super().__init__(**kwargs)
|
|
483
498
|
|
|
484
|
-
def
|
|
499
|
+
def generate_points(self) -> None:
|
|
485
500
|
# TODO: cache mobject in a re-importable way
|
|
486
501
|
|
|
487
502
|
self.handle_commands()
|
|
@@ -494,31 +509,102 @@ class VMobjectFromSVGPath(VMobject, metaclass=ConvertToOpenGL):
|
|
|
494
509
|
# Get rid of any null curves
|
|
495
510
|
self.set_points(self.get_points_without_null_curves())
|
|
496
511
|
|
|
497
|
-
|
|
512
|
+
def init_points(self) -> None:
|
|
513
|
+
self.generate_points()
|
|
498
514
|
|
|
499
515
|
def handle_commands(self) -> None:
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
516
|
+
all_points: list[np.ndarray] = []
|
|
517
|
+
last_move: np.ndarray = None
|
|
518
|
+
curve_start = None
|
|
519
|
+
last_true_move = None
|
|
520
|
+
|
|
521
|
+
def move_pen(pt: np.ndarray, *, true_move: bool = False) -> None:
|
|
522
|
+
nonlocal last_move, curve_start, last_true_move
|
|
523
|
+
last_move = pt
|
|
524
|
+
if curve_start is None:
|
|
525
|
+
curve_start = last_move
|
|
526
|
+
if true_move:
|
|
527
|
+
last_true_move = last_move
|
|
528
|
+
|
|
529
|
+
if self.n_points_per_curve == 4:
|
|
530
|
+
|
|
531
|
+
def add_cubic(
|
|
532
|
+
start: np.ndarray, cp1: np.ndarray, cp2: np.ndarray, end: np.ndarray
|
|
533
|
+
) -> None:
|
|
534
|
+
nonlocal all_points
|
|
535
|
+
assert len(all_points) % 4 == 0, len(all_points)
|
|
536
|
+
all_points += [start, cp1, cp2, end]
|
|
537
|
+
move_pen(end)
|
|
538
|
+
|
|
539
|
+
def add_quad(start: np.ndarray, cp: np.ndarray, end: np.ndarray) -> None:
|
|
540
|
+
add_cubic(start, (start + cp + cp) / 3, (cp + cp + end) / 3, end)
|
|
541
|
+
move_pen(end)
|
|
542
|
+
|
|
543
|
+
def add_line(start: np.ndarray, end: np.ndarray) -> None:
|
|
544
|
+
add_cubic(
|
|
545
|
+
start, (start + start + end) / 3, (start + end + end) / 3, end
|
|
546
|
+
)
|
|
547
|
+
move_pen(end)
|
|
548
|
+
|
|
549
|
+
else:
|
|
550
|
+
|
|
551
|
+
def add_cubic(
|
|
552
|
+
start: np.ndarray, cp1: np.ndarray, cp2: np.ndarray, end: np.ndarray
|
|
553
|
+
) -> None:
|
|
554
|
+
nonlocal all_points
|
|
555
|
+
assert len(all_points) % 3 == 0, len(all_points)
|
|
556
|
+
two_quads = get_quadratic_approximation_of_cubic(
|
|
557
|
+
start,
|
|
558
|
+
cp1,
|
|
559
|
+
cp2,
|
|
560
|
+
end,
|
|
561
|
+
)
|
|
562
|
+
all_points += two_quads[:3].tolist()
|
|
563
|
+
all_points += two_quads[3:].tolist()
|
|
564
|
+
move_pen(end)
|
|
565
|
+
|
|
566
|
+
def add_quad(start: np.ndarray, cp: np.ndarray, end: np.ndarray) -> None:
|
|
567
|
+
nonlocal all_points
|
|
568
|
+
assert len(all_points) % 3 == 0, len(all_points)
|
|
569
|
+
all_points += [start, cp, end]
|
|
570
|
+
move_pen(end)
|
|
571
|
+
|
|
572
|
+
def add_line(start: np.ndarray, end: np.ndarray) -> None:
|
|
573
|
+
add_quad(start, (start + end) / 2, end)
|
|
574
|
+
move_pen(end)
|
|
575
|
+
|
|
513
576
|
for segment in self.path_obj:
|
|
514
577
|
segment_class = segment.__class__
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
578
|
+
if segment_class == se.Move:
|
|
579
|
+
move_pen(_convert_point_to_3d(*segment.end), true_move=True)
|
|
580
|
+
elif segment_class == se.Line:
|
|
581
|
+
add_line(last_move, _convert_point_to_3d(*segment.end))
|
|
582
|
+
elif segment_class == se.QuadraticBezier:
|
|
583
|
+
add_quad(
|
|
584
|
+
last_move,
|
|
585
|
+
_convert_point_to_3d(*segment.control),
|
|
586
|
+
_convert_point_to_3d(*segment.end),
|
|
587
|
+
)
|
|
588
|
+
elif segment_class == se.CubicBezier:
|
|
589
|
+
add_cubic(
|
|
590
|
+
last_move,
|
|
591
|
+
_convert_point_to_3d(*segment.control1),
|
|
592
|
+
_convert_point_to_3d(*segment.control2),
|
|
593
|
+
_convert_point_to_3d(*segment.end),
|
|
594
|
+
)
|
|
595
|
+
elif segment_class == se.Close:
|
|
596
|
+
# If the SVG path naturally ends at the beginning of the curve,
|
|
597
|
+
# we do *not* need to draw a closing line. To account for floating
|
|
598
|
+
# point precision, we use a small value to compare the two points.
|
|
599
|
+
if abs(np.linalg.norm(last_move - last_true_move)) > 0.0001:
|
|
600
|
+
add_line(last_move, last_true_move)
|
|
601
|
+
curve_start = None
|
|
602
|
+
else:
|
|
603
|
+
raise AssertionError(f"Not implemented: {segment_class}")
|
|
604
|
+
|
|
605
|
+
self.points = np.array(all_points, ndmin=2, dtype="float64")
|
|
606
|
+
# If we have no points, make sure the array is shaped properly
|
|
607
|
+
# (0 rows tall by 3 columns wide) so future operations can
|
|
608
|
+
# add or remove points correctly.
|
|
609
|
+
if len(all_points) == 0:
|
|
610
|
+
self.points = np.reshape(self.points, (0, 3))
|