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.

Files changed (146) hide show
  1. manim/__init__.py +3 -6
  2. manim/__main__.py +61 -20
  3. manim/_config/__init__.py +6 -3
  4. manim/_config/cli_colors.py +16 -8
  5. manim/_config/default.cfg +1 -3
  6. manim/_config/logger_utils.py +14 -8
  7. manim/_config/utils.py +651 -472
  8. manim/animation/animation.py +152 -5
  9. manim/animation/composition.py +80 -39
  10. manim/animation/creation.py +196 -14
  11. manim/animation/fading.py +5 -9
  12. manim/animation/indication.py +103 -47
  13. manim/animation/movement.py +22 -5
  14. manim/animation/rotation.py +3 -2
  15. manim/animation/specialized.py +4 -6
  16. manim/animation/speedmodifier.py +10 -5
  17. manim/animation/transform.py +4 -5
  18. manim/animation/transform_matching_parts.py +1 -1
  19. manim/animation/updaters/mobject_update_utils.py +17 -14
  20. manim/camera/camera.py +15 -6
  21. manim/cli/__init__.py +17 -0
  22. manim/cli/cfg/group.py +70 -44
  23. manim/cli/checkhealth/checks.py +93 -75
  24. manim/cli/checkhealth/commands.py +14 -5
  25. manim/cli/default_group.py +157 -25
  26. manim/cli/init/commands.py +32 -24
  27. manim/cli/plugins/commands.py +16 -3
  28. manim/cli/render/commands.py +72 -60
  29. manim/cli/render/ease_of_access_options.py +4 -3
  30. manim/cli/render/global_options.py +51 -15
  31. manim/cli/render/output_options.py +6 -5
  32. manim/cli/render/render_options.py +97 -32
  33. manim/constants.py +65 -19
  34. manim/gui/gui.py +2 -0
  35. manim/mobject/frame.py +0 -1
  36. manim/mobject/geometry/arc.py +112 -78
  37. manim/mobject/geometry/boolean_ops.py +32 -25
  38. manim/mobject/geometry/labeled.py +300 -77
  39. manim/mobject/geometry/line.py +132 -64
  40. manim/mobject/geometry/polygram.py +126 -30
  41. manim/mobject/geometry/shape_matchers.py +35 -15
  42. manim/mobject/geometry/tips.py +38 -29
  43. manim/mobject/graph.py +414 -133
  44. manim/mobject/graphing/coordinate_systems.py +126 -64
  45. manim/mobject/graphing/functions.py +25 -15
  46. manim/mobject/graphing/number_line.py +24 -10
  47. manim/mobject/graphing/probability.py +2 -10
  48. manim/mobject/graphing/scale.py +6 -5
  49. manim/mobject/matrix.py +17 -19
  50. manim/mobject/mobject.py +314 -165
  51. manim/mobject/opengl/opengl_compatibility.py +2 -0
  52. manim/mobject/opengl/opengl_geometry.py +30 -9
  53. manim/mobject/opengl/opengl_image_mobject.py +2 -0
  54. manim/mobject/opengl/opengl_mobject.py +509 -343
  55. manim/mobject/opengl/opengl_point_cloud_mobject.py +5 -7
  56. manim/mobject/opengl/opengl_surface.py +3 -2
  57. manim/mobject/opengl/opengl_three_dimensions.py +2 -0
  58. manim/mobject/opengl/opengl_vectorized_mobject.py +46 -79
  59. manim/mobject/svg/brace.py +63 -13
  60. manim/mobject/svg/svg_mobject.py +4 -3
  61. manim/mobject/table.py +11 -13
  62. manim/mobject/text/code_mobject.py +186 -548
  63. manim/mobject/text/numbers.py +9 -7
  64. manim/mobject/text/tex_mobject.py +23 -14
  65. manim/mobject/text/text_mobject.py +70 -24
  66. manim/mobject/three_d/polyhedra.py +98 -1
  67. manim/mobject/three_d/three_d_utils.py +4 -4
  68. manim/mobject/three_d/three_dimensions.py +62 -34
  69. manim/mobject/types/image_mobject.py +42 -24
  70. manim/mobject/types/point_cloud_mobject.py +105 -67
  71. manim/mobject/types/vectorized_mobject.py +496 -228
  72. manim/mobject/value_tracker.py +5 -4
  73. manim/mobject/vector_field.py +5 -5
  74. manim/opengl/__init__.py +3 -3
  75. manim/plugins/__init__.py +14 -1
  76. manim/plugins/plugins_flags.py +14 -8
  77. manim/renderer/cairo_renderer.py +20 -10
  78. manim/renderer/opengl_renderer.py +21 -23
  79. manim/renderer/opengl_renderer_window.py +2 -0
  80. manim/renderer/shader.py +2 -3
  81. manim/renderer/shader_wrapper.py +5 -2
  82. manim/renderer/vectorized_mobject_rendering.py +5 -0
  83. manim/scene/moving_camera_scene.py +23 -0
  84. manim/scene/scene.py +90 -43
  85. manim/scene/scene_file_writer.py +316 -165
  86. manim/scene/section.py +17 -15
  87. manim/scene/three_d_scene.py +13 -21
  88. manim/scene/vector_space_scene.py +22 -9
  89. manim/typing.py +830 -70
  90. manim/utils/bezier.py +1667 -399
  91. manim/utils/caching.py +13 -5
  92. manim/utils/color/AS2700.py +2 -0
  93. manim/utils/color/BS381.py +3 -0
  94. manim/utils/color/DVIPSNAMES.py +96 -0
  95. manim/utils/color/SVGNAMES.py +179 -0
  96. manim/utils/color/X11.py +3 -0
  97. manim/utils/color/XKCD.py +3 -0
  98. manim/utils/color/__init__.py +8 -5
  99. manim/utils/color/core.py +844 -309
  100. manim/utils/color/manim_colors.py +7 -9
  101. manim/utils/commands.py +48 -20
  102. manim/utils/config_ops.py +18 -13
  103. manim/utils/debug.py +8 -7
  104. manim/utils/deprecation.py +90 -40
  105. manim/utils/docbuild/__init__.py +17 -0
  106. manim/utils/docbuild/autoaliasattr_directive.py +234 -0
  107. manim/utils/docbuild/autocolor_directive.py +21 -17
  108. manim/utils/docbuild/manim_directive.py +50 -35
  109. manim/utils/docbuild/module_parsing.py +245 -0
  110. manim/utils/exceptions.py +6 -0
  111. manim/utils/family.py +5 -3
  112. manim/utils/family_ops.py +17 -4
  113. manim/utils/file_ops.py +26 -16
  114. manim/utils/hashing.py +9 -7
  115. manim/utils/images.py +10 -4
  116. manim/utils/ipython_magic.py +14 -8
  117. manim/utils/iterables.py +161 -119
  118. manim/utils/module_ops.py +57 -19
  119. manim/utils/opengl.py +83 -24
  120. manim/utils/parameter_parsing.py +32 -0
  121. manim/utils/paths.py +21 -23
  122. manim/utils/polylabel.py +168 -0
  123. manim/utils/qhull.py +218 -0
  124. manim/utils/rate_functions.py +74 -39
  125. manim/utils/simple_functions.py +24 -15
  126. manim/utils/sounds.py +7 -1
  127. manim/utils/space_ops.py +125 -69
  128. manim/utils/testing/__init__.py +17 -0
  129. manim/utils/testing/_frames_testers.py +13 -8
  130. manim/utils/testing/_show_diff.py +5 -3
  131. manim/utils/testing/_test_class_makers.py +33 -18
  132. manim/utils/testing/frames_comparison.py +27 -19
  133. manim/utils/tex.py +127 -197
  134. manim/utils/tex_file_writing.py +47 -45
  135. manim/utils/tex_templates.py +2 -1
  136. manim/utils/unit.py +6 -5
  137. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/LICENSE.community +1 -1
  138. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/METADATA +40 -39
  139. manim-0.19.0.dist-info/RECORD +221 -0
  140. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/WHEEL +1 -1
  141. manim/cli/new/__init__.py +0 -0
  142. manim/cli/new/group.py +0 -189
  143. manim/plugins/import_plugins.py +0 -43
  144. manim-0.18.0.post0.dist-info/RECORD +0 -217
  145. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/LICENSE +0 -0
  146. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/entry_points.txt +0 -0
manim/utils/opengl.py CHANGED
@@ -1,24 +1,73 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing import TYPE_CHECKING
4
+
3
5
  import numpy as np
4
6
  import numpy.linalg as linalg
5
7
 
6
- from .. import config
8
+ from manim._config import config
9
+ from manim.typing import ManimFloat
10
+
11
+ if TYPE_CHECKING:
12
+ import numpy.typing as npt
13
+ from typing_extensions import TypeAlias
14
+
15
+ from manim.typing import MatrixMN, Point3D
7
16
 
8
- depth = 20
9
17
 
18
+ if TYPE_CHECKING:
19
+ from typing_extensions import TypeAlias
20
+
21
+ from manim.typing import MatrixMN
22
+
23
+
24
+ depth = 20
10
25
 
11
- def matrix_to_shader_input(matrix):
26
+ __all__ = [
27
+ "matrix_to_shader_input",
28
+ "orthographic_projection_matrix",
29
+ "perspective_projection_matrix",
30
+ "translation_matrix",
31
+ "x_rotation_matrix",
32
+ "y_rotation_matrix",
33
+ "z_rotation_matrix",
34
+ "rotate_in_place_matrix",
35
+ "rotation_matrix",
36
+ "scale_matrix",
37
+ "view_matrix",
38
+ ]
39
+
40
+ FlattenedMatrix4x4: TypeAlias = tuple[
41
+ float,
42
+ float,
43
+ float,
44
+ float,
45
+ float,
46
+ float,
47
+ float,
48
+ float,
49
+ float,
50
+ float,
51
+ float,
52
+ float,
53
+ float,
54
+ float,
55
+ float,
56
+ float,
57
+ ]
58
+
59
+
60
+ def matrix_to_shader_input(matrix: MatrixMN) -> FlattenedMatrix4x4:
12
61
  return tuple(matrix.T.ravel())
13
62
 
14
63
 
15
64
  def orthographic_projection_matrix(
16
- width=None,
17
- height=None,
18
- near=1,
19
- far=depth + 1,
20
- format=True,
21
- ):
65
+ width: float | None = None,
66
+ height: float | None = None,
67
+ near: float = 1,
68
+ far: float = depth + 1,
69
+ format_: bool = True,
70
+ ) -> MatrixMN | FlattenedMatrix4x4:
22
71
  if width is None:
23
72
  width = config["frame_width"]
24
73
  if height is None:
@@ -31,13 +80,19 @@ def orthographic_projection_matrix(
31
80
  [0, 0, 0, 1],
32
81
  ],
33
82
  )
34
- if format:
83
+ if format_:
35
84
  return matrix_to_shader_input(projection_matrix)
36
85
  else:
37
86
  return projection_matrix
38
87
 
39
88
 
40
- def perspective_projection_matrix(width=None, height=None, near=2, far=50, format=True):
89
+ def perspective_projection_matrix(
90
+ width: float | None = None,
91
+ height: float | None = None,
92
+ near: float = 2,
93
+ far: float = 50,
94
+ format_: bool = True,
95
+ ) -> MatrixMN | FlattenedMatrix4x4:
41
96
  if width is None:
42
97
  width = config["frame_width"] / 6
43
98
  if height is None:
@@ -50,13 +105,13 @@ def perspective_projection_matrix(width=None, height=None, near=2, far=50, forma
50
105
  [0, 0, -1, 0],
51
106
  ],
52
107
  )
53
- if format:
108
+ if format_:
54
109
  return matrix_to_shader_input(projection_matrix)
55
110
  else:
56
111
  return projection_matrix
57
112
 
58
113
 
59
- def translation_matrix(x=0, y=0, z=0):
114
+ def translation_matrix(x: float = 0, y: float = 0, z: float = 0) -> MatrixMN:
60
115
  return np.array(
61
116
  [
62
117
  [1, 0, 0, x],
@@ -64,10 +119,11 @@ def translation_matrix(x=0, y=0, z=0):
64
119
  [0, 0, 1, z],
65
120
  [0, 0, 0, 1],
66
121
  ],
122
+ dtype=ManimFloat,
67
123
  )
68
124
 
69
125
 
70
- def x_rotation_matrix(x=0):
126
+ def x_rotation_matrix(x: float = 0) -> MatrixMN:
71
127
  return np.array(
72
128
  [
73
129
  [1, 0, 0, 0],
@@ -78,7 +134,7 @@ def x_rotation_matrix(x=0):
78
134
  )
79
135
 
80
136
 
81
- def y_rotation_matrix(y=0):
137
+ def y_rotation_matrix(y: float = 0) -> MatrixMN:
82
138
  return np.array(
83
139
  [
84
140
  [np.cos(y), 0, np.sin(y), 0],
@@ -89,7 +145,7 @@ def y_rotation_matrix(y=0):
89
145
  )
90
146
 
91
147
 
92
- def z_rotation_matrix(z=0):
148
+ def z_rotation_matrix(z: float = 0) -> MatrixMN:
93
149
  return np.array(
94
150
  [
95
151
  [np.cos(z), -np.sin(z), 0, 0],
@@ -101,7 +157,9 @@ def z_rotation_matrix(z=0):
101
157
 
102
158
 
103
159
  # TODO: When rotating around the x axis, rotation eventually stops.
104
- def rotate_in_place_matrix(initial_position, x=0, y=0, z=0):
160
+ def rotate_in_place_matrix(
161
+ initial_position: Point3D, x: float = 0, y: float = 0, z: float = 0
162
+ ) -> MatrixMN:
105
163
  return np.matmul(
106
164
  translation_matrix(*-initial_position),
107
165
  np.matmul(
@@ -111,14 +169,14 @@ def rotate_in_place_matrix(initial_position, x=0, y=0, z=0):
111
169
  )
112
170
 
113
171
 
114
- def rotation_matrix(x=0, y=0, z=0):
172
+ def rotation_matrix(x: float = 0, y: float = 0, z: float = 0) -> MatrixMN:
115
173
  return np.matmul(
116
174
  np.matmul(x_rotation_matrix(x), y_rotation_matrix(y)),
117
175
  z_rotation_matrix(z),
118
176
  )
119
177
 
120
178
 
121
- def scale_matrix(scale_factor=1):
179
+ def scale_matrix(scale_factor: float = 1) -> npt.NDArray:
122
180
  return np.array(
123
181
  [
124
182
  [scale_factor, 0, 0, 0],
@@ -126,15 +184,16 @@ def scale_matrix(scale_factor=1):
126
184
  [0, 0, scale_factor, 0],
127
185
  [0, 0, 0, 1],
128
186
  ],
187
+ dtype=ManimFloat,
129
188
  )
130
189
 
131
190
 
132
191
  def view_matrix(
133
- translation=None,
134
- x_rotation=0,
135
- y_rotation=0,
136
- z_rotation=0,
137
- ):
192
+ translation: Point3D | None = None,
193
+ x_rotation: float = 0,
194
+ y_rotation: float = 0,
195
+ z_rotation: float = 0,
196
+ ) -> MatrixMN:
138
197
  if translation is None:
139
198
  translation = np.array([0, 0, depth / 2 + 1])
140
199
  model_matrix = np.matmul(
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+ from types import GeneratorType
5
+ from typing import TypeVar
6
+
7
+ T = TypeVar("T")
8
+
9
+
10
+ def flatten_iterable_parameters(
11
+ args: Iterable[T | Iterable[T] | GeneratorType],
12
+ ) -> list[T]:
13
+ """Flattens an iterable of parameters into a list of parameters.
14
+
15
+ Parameters
16
+ ----------
17
+ args
18
+ The iterable of parameters to flatten.
19
+ [(generator), [], (), ...]
20
+
21
+ Returns
22
+ -------
23
+ :class:`list`
24
+ The flattened list of parameters.
25
+ """
26
+ flattened_parameters: list[T] = []
27
+ for arg in args:
28
+ if isinstance(arg, (Iterable, GeneratorType)):
29
+ flattened_parameters.extend(arg)
30
+ else:
31
+ flattened_parameters.append(arg)
32
+ return flattened_parameters
manim/utils/paths.py CHANGED
@@ -10,28 +10,22 @@ __all__ = [
10
10
  ]
11
11
 
12
12
 
13
- from typing import Callable
13
+ from typing import TYPE_CHECKING
14
14
 
15
15
  import numpy as np
16
16
 
17
17
  from ..constants import OUT
18
18
  from ..utils.bezier import interpolate
19
- from ..utils.deprecation import deprecated_params
20
19
  from ..utils.space_ops import rotation_matrix
21
20
 
22
- STRAIGHT_PATH_THRESHOLD = 0.01
21
+ if TYPE_CHECKING:
22
+ from manim.typing import PathFuncType, Point3D_Array, Vector3D
23
+
23
24
 
24
- PATH_FUNC_TYPE = Callable[[np.ndarray, np.ndarray, float], np.ndarray]
25
+ STRAIGHT_PATH_THRESHOLD = 0.01
25
26
 
26
27
 
27
- # Remove `*args` and the `if` inside the functions when removing deprecation
28
- @deprecated_params(
29
- params="start_points, end_points, alpha",
30
- since="v0.14",
31
- until="v0.15",
32
- message="Straight path is now returning interpolating function to make it consistent with other path functions. Use straight_path()(a,b,c) instead of straight_path(a,b,c).",
33
- )
34
- def straight_path(*args) -> PATH_FUNC_TYPE:
28
+ def straight_path() -> PathFuncType:
35
29
  """Simplest path function. Each point in a set goes in a straight path toward its destination.
36
30
 
37
31
  Examples
@@ -74,14 +68,12 @@ def straight_path(*args) -> PATH_FUNC_TYPE:
74
68
  self.wait()
75
69
 
76
70
  """
77
- if len(args) > 0:
78
- return interpolate(*args)
79
71
  return interpolate
80
72
 
81
73
 
82
74
  def path_along_circles(
83
- arc_angle: float, circles_centers: np.ndarray, axis: np.ndarray = OUT
84
- ) -> PATH_FUNC_TYPE:
75
+ arc_angle: float, circles_centers: np.ndarray, axis: Vector3D = OUT
76
+ ) -> PathFuncType:
85
77
  """This function transforms each point by moving it roughly along a circle, each with its own specified center.
86
78
 
87
79
  The path may be seen as each point smoothly changing its orbit from its starting position to its destination.
@@ -144,7 +136,9 @@ def path_along_circles(
144
136
  axis = OUT
145
137
  unit_axis = axis / np.linalg.norm(axis)
146
138
 
147
- def path(start_points: np.ndarray, end_points: np.ndarray, alpha: float):
139
+ def path(
140
+ start_points: Point3D_Array, end_points: Point3D_Array, alpha: float
141
+ ) -> Point3D_Array:
148
142
  detransformed_end_points = circles_centers + np.dot(
149
143
  end_points - circles_centers, rotation_matrix(-arc_angle, unit_axis).T
150
144
  )
@@ -158,7 +152,7 @@ def path_along_circles(
158
152
  return path
159
153
 
160
154
 
161
- def path_along_arc(arc_angle: float, axis: np.ndarray = OUT) -> PATH_FUNC_TYPE:
155
+ def path_along_arc(arc_angle: float, axis: Vector3D = OUT) -> PathFuncType:
162
156
  """This function transforms each point by moving it along a circular arc.
163
157
 
164
158
  Parameters
@@ -214,7 +208,9 @@ def path_along_arc(arc_angle: float, axis: np.ndarray = OUT) -> PATH_FUNC_TYPE:
214
208
  axis = OUT
215
209
  unit_axis = axis / np.linalg.norm(axis)
216
210
 
217
- def path(start_points: np.ndarray, end_points: np.ndarray, alpha: float):
211
+ def path(
212
+ start_points: Point3D_Array, end_points: Point3D_Array, alpha: float
213
+ ) -> Point3D_Array:
218
214
  vects = end_points - start_points
219
215
  centers = start_points + 0.5 * vects
220
216
  if arc_angle != np.pi:
@@ -225,7 +221,7 @@ def path_along_arc(arc_angle: float, axis: np.ndarray = OUT) -> PATH_FUNC_TYPE:
225
221
  return path
226
222
 
227
223
 
228
- def clockwise_path() -> PATH_FUNC_TYPE:
224
+ def clockwise_path() -> PathFuncType:
229
225
  """This function transforms each point by moving clockwise around a half circle.
230
226
 
231
227
  Examples
@@ -271,7 +267,7 @@ def clockwise_path() -> PATH_FUNC_TYPE:
271
267
  return path_along_arc(-np.pi)
272
268
 
273
269
 
274
- def counterclockwise_path() -> PATH_FUNC_TYPE:
270
+ def counterclockwise_path() -> PathFuncType:
275
271
  """This function transforms each point by moving counterclockwise around a half circle.
276
272
 
277
273
  Examples
@@ -317,7 +313,7 @@ def counterclockwise_path() -> PATH_FUNC_TYPE:
317
313
  return path_along_arc(np.pi)
318
314
 
319
315
 
320
- def spiral_path(angle: float, axis: np.ndarray = OUT) -> PATH_FUNC_TYPE:
316
+ def spiral_path(angle: float, axis: Vector3D = OUT) -> PathFuncType:
321
317
  """This function transforms each point by moving along a spiral to its destination.
322
318
 
323
319
  Parameters
@@ -373,7 +369,9 @@ def spiral_path(angle: float, axis: np.ndarray = OUT) -> PATH_FUNC_TYPE:
373
369
  axis = OUT
374
370
  unit_axis = axis / np.linalg.norm(axis)
375
371
 
376
- def path(start_points: np.ndarray, end_points: np.ndarray, alpha: float):
372
+ def path(
373
+ start_points: Point3D_Array, end_points: Point3D_Array, alpha: float
374
+ ) -> Point3D_Array:
377
375
  rot_matrix = rotation_matrix((alpha - 1) * angle, unit_axis)
378
376
  return start_points + alpha * np.dot(end_points - start_points, rot_matrix.T)
379
377
 
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env python
2
+ from __future__ import annotations
3
+
4
+ from queue import PriorityQueue
5
+ from typing import TYPE_CHECKING
6
+
7
+ import numpy as np
8
+
9
+ if TYPE_CHECKING:
10
+ from collections.abc import Sequence
11
+
12
+ from manim.typing import (
13
+ Point2D,
14
+ Point2D_Array,
15
+ Point2DLike,
16
+ Point2DLike_Array,
17
+ Point3DLike_Array,
18
+ )
19
+
20
+
21
+ class Polygon:
22
+ """
23
+ Initializes the Polygon with the given rings.
24
+
25
+ Parameters
26
+ ----------
27
+ rings
28
+ A collection of closed polygonal ring.
29
+ """
30
+
31
+ def __init__(self, rings: Sequence[Point2DLike_Array]) -> None:
32
+ np_rings: list[Point2D_Array] = [np.asarray(ring) for ring in rings]
33
+ # Flatten Array
34
+ csum = np.cumsum([ring.shape[0] for ring in np_rings])
35
+ self.array: Point2D_Array = np.concatenate(np_rings, axis=0)
36
+
37
+ # Compute Boundary
38
+ self.start: Point2D_Array = np.delete(self.array, csum - 1, axis=0)
39
+ self.stop: Point2D_Array = np.delete(self.array, csum % csum[-1], axis=0)
40
+ self.diff: Point2D_Array = np.delete(
41
+ np.diff(self.array, axis=0), csum[:-1] - 1, axis=0
42
+ )
43
+ self.norm: Point2D_Array = self.diff / np.einsum(
44
+ "ij,ij->i", self.diff, self.diff
45
+ ).reshape(-1, 1)
46
+
47
+ # Compute Centroid
48
+ x, y = self.start[:, 0], self.start[:, 1]
49
+ xr, yr = self.stop[:, 0], self.stop[:, 1]
50
+ self.area: float = 0.5 * (np.dot(x, yr) - np.dot(xr, y))
51
+ if self.area:
52
+ factor = x * yr - xr * y
53
+ cx = np.sum((x + xr) * factor) / (6.0 * self.area)
54
+ cy = np.sum((y + yr) * factor) / (6.0 * self.area)
55
+ self.centroid = np.array([cx, cy])
56
+
57
+ def compute_distance(self, point: Point2DLike) -> float:
58
+ """Compute the minimum distance from a point to the polygon."""
59
+ scalars = np.einsum("ij,ij->i", self.norm, point - self.start)
60
+ clips = np.clip(scalars, 0, 1).reshape(-1, 1)
61
+ d: float = np.min(
62
+ np.linalg.norm(self.start + self.diff * clips - point, axis=1)
63
+ )
64
+ return d if self.inside(point) else -d
65
+
66
+ def inside(self, point: Point2DLike) -> bool:
67
+ """Check if a point is inside the polygon."""
68
+ # Views
69
+ px, py = point
70
+ x, y = self.start[:, 0], self.start[:, 1]
71
+ xr, yr = self.stop[:, 0], self.stop[:, 1]
72
+
73
+ # Count Crossings (enforce short-circuit)
74
+ c = (y > py) != (yr > py)
75
+ c = px < x[c] + (py - y[c]) * (xr[c] - x[c]) / (yr[c] - y[c])
76
+ c_sum: int = np.sum(c)
77
+ return c_sum % 2 == 1
78
+
79
+
80
+ class Cell:
81
+ """
82
+ A square in a mesh covering the :class:`~.Polygon` passed as an argument.
83
+
84
+ Parameters
85
+ ----------
86
+ c
87
+ Center coordinates of the Cell.
88
+ h
89
+ Half-Size of the Cell.
90
+ polygon
91
+ :class:`~.Polygon` object for which the distance is computed.
92
+ """
93
+
94
+ def __init__(self, c: Point2DLike, h: float, polygon: Polygon) -> None:
95
+ self.c: Point2D = np.asarray(c)
96
+ self.h = h
97
+ self.d = polygon.compute_distance(self.c)
98
+ self.p = self.d + self.h * np.sqrt(2)
99
+
100
+ def __lt__(self, other: Cell) -> bool:
101
+ return self.d < other.d
102
+
103
+ def __gt__(self, other: Cell) -> bool:
104
+ return self.d > other.d
105
+
106
+ def __le__(self, other: Cell) -> bool:
107
+ return self.d <= other.d
108
+
109
+ def __ge__(self, other: Cell) -> bool:
110
+ return self.d >= other.d
111
+
112
+
113
+ def polylabel(rings: Sequence[Point3DLike_Array], precision: float = 0.01) -> Cell:
114
+ """
115
+ Finds the pole of inaccessibility (the point that is farthest from the edges of the polygon)
116
+ using an iterative grid-based approach.
117
+
118
+ Parameters
119
+ ----------
120
+ rings
121
+ A list of lists, where each list is a sequence of points representing the rings of the polygon.
122
+ Typically, multiple rings indicate holes in the polygon.
123
+ precision
124
+ The precision of the result (default is 0.01).
125
+
126
+ Returns
127
+ -------
128
+ Cell
129
+ A Cell containing the pole of inaccessibility to a given precision.
130
+ """
131
+ # Precompute Polygon Data
132
+ np_rings: list[Point2D_Array] = [np.asarray(ring)[:, :2] for ring in rings]
133
+ polygon = Polygon(np_rings)
134
+
135
+ # Bounding Box
136
+ mins = np.min(polygon.array, axis=0)
137
+ maxs = np.max(polygon.array, axis=0)
138
+ dims = maxs - mins
139
+ s = np.min(dims)
140
+ h = s / 2.0
141
+
142
+ # Initial Grid
143
+ queue: PriorityQueue[Cell] = PriorityQueue()
144
+ xv, yv = np.meshgrid(np.arange(mins[0], maxs[0], s), np.arange(mins[1], maxs[1], s))
145
+ for corner in np.vstack([xv.ravel(), yv.ravel()]).T:
146
+ queue.put(Cell(corner + h, h, polygon))
147
+
148
+ # Initial Guess
149
+ best = Cell(polygon.centroid, 0, polygon)
150
+ bbox = Cell(mins + (dims / 2), 0, polygon)
151
+ if bbox.d > best.d:
152
+ best = bbox
153
+
154
+ # While there are cells to consider...
155
+ directions = np.array([[-1, -1], [1, -1], [-1, 1], [1, 1]])
156
+ while not queue.empty():
157
+ cell = queue.get()
158
+ if cell > best:
159
+ best = cell
160
+ # If a cell is promising, subdivide!
161
+ if cell.p - best.d > precision:
162
+ h = cell.h / 2.0
163
+ offsets = cell.c + directions * h
164
+ queue.put(Cell(offsets[0], h, polygon))
165
+ queue.put(Cell(offsets[1], h, polygon))
166
+ queue.put(Cell(offsets[2], h, polygon))
167
+ queue.put(Cell(offsets[3], h, polygon))
168
+ return best