amulet-core 2.0a5__cp311-cp311-macosx_10_9_universal2.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 amulet-core might be problematic. Click here for more details.

Files changed (210) hide show
  1. amulet/__init__.cpython-311-darwin.so +0 -0
  2. amulet/__init__.pyi +30 -0
  3. amulet/__pyinstaller/__init__.py +2 -0
  4. amulet/__pyinstaller/hook-amulet.py +4 -0
  5. amulet/_init.py +28 -0
  6. amulet/_version.py +21 -0
  7. amulet/biome.cpp +36 -0
  8. amulet/biome.hpp +43 -0
  9. amulet/biome.pyi +77 -0
  10. amulet/block.cpp +435 -0
  11. amulet/block.hpp +119 -0
  12. amulet/block.pyi +273 -0
  13. amulet/block_entity.cpp +12 -0
  14. amulet/block_entity.hpp +56 -0
  15. amulet/block_entity.pyi +80 -0
  16. amulet/chunk.cpp +16 -0
  17. amulet/chunk.hpp +99 -0
  18. amulet/chunk.pyi +30 -0
  19. amulet/chunk_/components/biome.py +155 -0
  20. amulet/chunk_/components/block_entity.py +117 -0
  21. amulet/chunk_/components/entity.py +64 -0
  22. amulet/chunk_/components/height_2d.py +16 -0
  23. amulet/chunk_components.pyi +95 -0
  24. amulet/collections.pyi +37 -0
  25. amulet/data_types.py +29 -0
  26. amulet/entity.py +180 -0
  27. amulet/errors.py +63 -0
  28. amulet/game/__init__.py +7 -0
  29. amulet/game/_game.py +152 -0
  30. amulet/game/_universal/__init__.py +1 -0
  31. amulet/game/_universal/_biome.py +17 -0
  32. amulet/game/_universal/_block.py +47 -0
  33. amulet/game/_universal/_version.py +68 -0
  34. amulet/game/abc/__init__.py +22 -0
  35. amulet/game/abc/_block_specification.py +150 -0
  36. amulet/game/abc/biome.py +213 -0
  37. amulet/game/abc/block.py +331 -0
  38. amulet/game/abc/game_version_container.py +25 -0
  39. amulet/game/abc/json_interface.py +27 -0
  40. amulet/game/abc/version.py +44 -0
  41. amulet/game/bedrock/__init__.py +1 -0
  42. amulet/game/bedrock/_biome.py +35 -0
  43. amulet/game/bedrock/_block.py +42 -0
  44. amulet/game/bedrock/_version.py +165 -0
  45. amulet/game/java/__init__.py +2 -0
  46. amulet/game/java/_biome.py +35 -0
  47. amulet/game/java/_block.py +60 -0
  48. amulet/game/java/_version.py +176 -0
  49. amulet/game/translate/__init__.py +12 -0
  50. amulet/game/translate/_functions/__init__.py +15 -0
  51. amulet/game/translate/_functions/_code_functions/__init__.py +0 -0
  52. amulet/game/translate/_functions/_code_functions/_text.py +553 -0
  53. amulet/game/translate/_functions/_code_functions/banner_pattern.py +67 -0
  54. amulet/game/translate/_functions/_code_functions/bedrock_chest_connection.py +152 -0
  55. amulet/game/translate/_functions/_code_functions/bedrock_moving_block_pos.py +88 -0
  56. amulet/game/translate/_functions/_code_functions/bedrock_sign.py +152 -0
  57. amulet/game/translate/_functions/_code_functions/bedrock_skull_rotation.py +16 -0
  58. amulet/game/translate/_functions/_code_functions/custom_name.py +146 -0
  59. amulet/game/translate/_functions/_frozen.py +66 -0
  60. amulet/game/translate/_functions/_state.py +54 -0
  61. amulet/game/translate/_functions/_typing.py +98 -0
  62. amulet/game/translate/_functions/abc.py +116 -0
  63. amulet/game/translate/_functions/carry_nbt.py +160 -0
  64. amulet/game/translate/_functions/carry_properties.py +80 -0
  65. amulet/game/translate/_functions/code.py +143 -0
  66. amulet/game/translate/_functions/map_block_name.py +66 -0
  67. amulet/game/translate/_functions/map_nbt.py +111 -0
  68. amulet/game/translate/_functions/map_properties.py +93 -0
  69. amulet/game/translate/_functions/multiblock.py +112 -0
  70. amulet/game/translate/_functions/new_block.py +42 -0
  71. amulet/game/translate/_functions/new_entity.py +43 -0
  72. amulet/game/translate/_functions/new_nbt.py +206 -0
  73. amulet/game/translate/_functions/new_properties.py +64 -0
  74. amulet/game/translate/_functions/sequence.py +51 -0
  75. amulet/game/translate/_functions/walk_input_nbt.py +331 -0
  76. amulet/game/translate/_translator.py +433 -0
  77. amulet/item.py +75 -0
  78. amulet/level/__init__.pyi +27 -0
  79. amulet/level/_load.py +100 -0
  80. amulet/level/abc/__init__.py +12 -0
  81. amulet/level/abc/_chunk_handle.py +335 -0
  82. amulet/level/abc/_dimension.py +86 -0
  83. amulet/level/abc/_history/__init__.py +1 -0
  84. amulet/level/abc/_history/_cache.py +224 -0
  85. amulet/level/abc/_history/_history_manager.py +291 -0
  86. amulet/level/abc/_level/__init__.py +5 -0
  87. amulet/level/abc/_level/_compactable_level.py +10 -0
  88. amulet/level/abc/_level/_creatable_level.py +29 -0
  89. amulet/level/abc/_level/_disk_level.py +17 -0
  90. amulet/level/abc/_level/_level.py +453 -0
  91. amulet/level/abc/_level/_loadable_level.py +42 -0
  92. amulet/level/abc/_player_storage.py +7 -0
  93. amulet/level/abc/_raw_level.py +187 -0
  94. amulet/level/abc/_registry.py +40 -0
  95. amulet/level/bedrock/__init__.py +2 -0
  96. amulet/level/bedrock/_chunk_handle.py +19 -0
  97. amulet/level/bedrock/_dimension.py +22 -0
  98. amulet/level/bedrock/_level.py +187 -0
  99. amulet/level/bedrock/_raw/__init__.py +5 -0
  100. amulet/level/bedrock/_raw/_actor_counter.py +53 -0
  101. amulet/level/bedrock/_raw/_chunk.py +54 -0
  102. amulet/level/bedrock/_raw/_chunk_decode.py +668 -0
  103. amulet/level/bedrock/_raw/_chunk_encode.py +602 -0
  104. amulet/level/bedrock/_raw/_constant.py +9 -0
  105. amulet/level/bedrock/_raw/_dimension.py +343 -0
  106. amulet/level/bedrock/_raw/_level.py +463 -0
  107. amulet/level/bedrock/_raw/_level_dat.py +90 -0
  108. amulet/level/bedrock/_raw/_typing.py +6 -0
  109. amulet/level/bedrock/_raw/leveldb_chunk_versions.py +83 -0
  110. amulet/level/bedrock/chunk/__init__.py +1 -0
  111. amulet/level/bedrock/chunk/_chunk.py +126 -0
  112. amulet/level/bedrock/chunk/components/__init__.py +0 -0
  113. amulet/level/bedrock/chunk/components/chunk_version.py +12 -0
  114. amulet/level/bedrock/chunk/components/finalised_state.py +13 -0
  115. amulet/level/bedrock/chunk/components/raw_chunk.py +15 -0
  116. amulet/level/construction/__init__.py +0 -0
  117. amulet/level/java/__init__.pyi +21 -0
  118. amulet/level/java/_chunk_handle.py +17 -0
  119. amulet/level/java/_chunk_handle.pyi +15 -0
  120. amulet/level/java/_dimension.py +20 -0
  121. amulet/level/java/_dimension.pyi +13 -0
  122. amulet/level/java/_level.py +184 -0
  123. amulet/level/java/_level.pyi +120 -0
  124. amulet/level/java/_raw/__init__.pyi +19 -0
  125. amulet/level/java/_raw/_chunk.pyi +23 -0
  126. amulet/level/java/_raw/_chunk_decode.py +561 -0
  127. amulet/level/java/_raw/_chunk_encode.py +463 -0
  128. amulet/level/java/_raw/_constant.py +9 -0
  129. amulet/level/java/_raw/_constant.pyi +20 -0
  130. amulet/level/java/_raw/_data_pack/__init__.py +2 -0
  131. amulet/level/java/_raw/_data_pack/__init__.pyi +8 -0
  132. amulet/level/java/_raw/_data_pack/data_pack.py +241 -0
  133. amulet/level/java/_raw/_data_pack/data_pack.pyi +197 -0
  134. amulet/level/java/_raw/_data_pack/data_pack_manager.py +77 -0
  135. amulet/level/java/_raw/_data_pack/data_pack_manager.pyi +75 -0
  136. amulet/level/java/_raw/_dimension.py +86 -0
  137. amulet/level/java/_raw/_dimension.pyi +72 -0
  138. amulet/level/java/_raw/_level.py +507 -0
  139. amulet/level/java/_raw/_level.pyi +238 -0
  140. amulet/level/java/_raw/_typing.py +3 -0
  141. amulet/level/java/_raw/_typing.pyi +5 -0
  142. amulet/level/java/anvil/__init__.py +2 -0
  143. amulet/level/java/anvil/__init__.pyi +11 -0
  144. amulet/level/java/anvil/_dimension.py +170 -0
  145. amulet/level/java/anvil/_dimension.pyi +109 -0
  146. amulet/level/java/anvil/_region.py +421 -0
  147. amulet/level/java/anvil/_region.pyi +197 -0
  148. amulet/level/java/anvil/_sector_manager.py +223 -0
  149. amulet/level/java/anvil/_sector_manager.pyi +142 -0
  150. amulet/level/java/chunk.pyi +81 -0
  151. amulet/level/java/chunk_/_chunk.py +260 -0
  152. amulet/level/java/chunk_/components/inhabited_time.py +12 -0
  153. amulet/level/java/chunk_/components/last_update.py +12 -0
  154. amulet/level/java/chunk_/components/legacy_version.py +12 -0
  155. amulet/level/java/chunk_/components/light_populated.py +12 -0
  156. amulet/level/java/chunk_/components/named_height_2d.py +37 -0
  157. amulet/level/java/chunk_/components/status.py +11 -0
  158. amulet/level/java/chunk_/components/terrain_populated.py +12 -0
  159. amulet/level/java/chunk_components.pyi +22 -0
  160. amulet/level/java/long_array.pyi +38 -0
  161. amulet/level/java_forge/__init__.py +0 -0
  162. amulet/level/mcstructure/__init__.py +0 -0
  163. amulet/level/nbt/__init__.py +0 -0
  164. amulet/level/schematic/__init__.py +0 -0
  165. amulet/level/sponge_schematic/__init__.py +0 -0
  166. amulet/level/temporary_level/__init__.py +1 -0
  167. amulet/level/temporary_level/_level.py +16 -0
  168. amulet/palette/__init__.pyi +8 -0
  169. amulet/palette/biome_palette.pyi +45 -0
  170. amulet/palette/block_palette.pyi +45 -0
  171. amulet/player.py +64 -0
  172. amulet/py.typed +0 -0
  173. amulet/selection/__init__.py +2 -0
  174. amulet/selection/abstract_selection.py +342 -0
  175. amulet/selection/box.py +852 -0
  176. amulet/selection/group.py +481 -0
  177. amulet/utils/__init__.pyi +28 -0
  178. amulet/utils/call_spec/__init__.py +24 -0
  179. amulet/utils/call_spec/__init__.pyi +53 -0
  180. amulet/utils/call_spec/_call_spec.py +262 -0
  181. amulet/utils/call_spec/_call_spec.pyi +272 -0
  182. amulet/utils/format_utils.py +41 -0
  183. amulet/utils/generator.py +18 -0
  184. amulet/utils/matrix.py +243 -0
  185. amulet/utils/matrix.pyi +177 -0
  186. amulet/utils/numpy.pyi +11 -0
  187. amulet/utils/numpy_helpers.py +19 -0
  188. amulet/utils/shareable_lock.py +335 -0
  189. amulet/utils/shareable_lock.pyi +190 -0
  190. amulet/utils/signal/__init__.py +10 -0
  191. amulet/utils/signal/__init__.pyi +25 -0
  192. amulet/utils/signal/_signal.py +228 -0
  193. amulet/utils/signal/_signal.pyi +84 -0
  194. amulet/utils/task_manager.py +235 -0
  195. amulet/utils/task_manager.pyi +168 -0
  196. amulet/utils/typed_property.py +111 -0
  197. amulet/utils/typing.py +4 -0
  198. amulet/utils/typing.pyi +6 -0
  199. amulet/utils/weakref.py +70 -0
  200. amulet/utils/weakref.pyi +50 -0
  201. amulet/utils/world_utils.py +102 -0
  202. amulet/utils/world_utils.pyi +109 -0
  203. amulet/version.cpp +136 -0
  204. amulet/version.hpp +142 -0
  205. amulet/version.pyi +94 -0
  206. amulet_core-2.0a5.dist-info/METADATA +103 -0
  207. amulet_core-2.0a5.dist-info/RECORD +210 -0
  208. amulet_core-2.0a5.dist-info/WHEEL +5 -0
  209. amulet_core-2.0a5.dist-info/entry_points.txt +2 -0
  210. amulet_core-2.0a5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,481 @@
1
+ from __future__ import annotations
2
+
3
+ import numpy
4
+
5
+ from typing import Iterable, Iterator, overload, Any
6
+ import warnings
7
+
8
+ from amulet.data_types import (
9
+ BlockCoordinates,
10
+ ChunkCoordinates,
11
+ SubChunkCoordinates,
12
+ FloatTriplet,
13
+ )
14
+ from amulet.data_types import (
15
+ PointCoordinates,
16
+ PointCoordinatesArray,
17
+ )
18
+ from .abstract_selection import AbstractBaseSelection
19
+ from .box import SelectionBox
20
+
21
+
22
+ class SelectionGroup(AbstractBaseSelection, Iterable[SelectionBox]):
23
+ """
24
+ A container for zero or more :class:`SelectionBox` instances.
25
+
26
+ This allows for non-rectangular and non-contiguous selections.
27
+ """
28
+
29
+ _selection_boxes: tuple[SelectionBox, ...]
30
+
31
+ def __init__(self, selection_boxes: SelectionBox | Iterable[SelectionBox] = ()):
32
+ """
33
+ Construct a new :class:`SelectionGroup` class from the given data.
34
+
35
+ >>> SelectionGroup(SelectionBox((0, 0, 0), (1, 1, 1)))
36
+ >>> SelectionGroup([
37
+ >>> SelectionBox((0, 0, 0), (1, 1, 1)),
38
+ >>> SelectionBox((1, 1, 1), (2, 2, 2))
39
+ >>> ])
40
+
41
+ :param selection_boxes: A :class:`SelectionBox` or iterable of :class:`SelectionBox` classes.
42
+ """
43
+ if isinstance(selection_boxes, SelectionBox):
44
+ self._selection_boxes = (selection_boxes,)
45
+ else:
46
+ self._selection_boxes = tuple(
47
+ box for box in selection_boxes if isinstance(box, SelectionBox)
48
+ )
49
+
50
+ def __repr__(self) -> str:
51
+ boxes = ", ".join([repr(box) for box in self.selection_boxes])
52
+ return f"SelectionGroup([{boxes}])"
53
+
54
+ def __str__(self) -> str:
55
+ boxes = ", ".join([str(box) for box in self.selection_boxes])
56
+ return f"[{boxes}]"
57
+
58
+ def __eq__(self, other: Any) -> bool:
59
+ """
60
+ Does the contents of this :class:`SelectionGroup` match the other :class:`SelectionGroup`.
61
+
62
+ Note if the boxes do not exactly match this will return False even if the volume represented is the same.
63
+
64
+ :param other: The other :class:`SelectionGroup` to compare with.
65
+ :return: True if the boxes contained match.
66
+ """
67
+ if not isinstance(other, SelectionGroup):
68
+ return NotImplemented
69
+ return self.selection_boxes_sorted == other.selection_boxes_sorted
70
+
71
+ def __add__(self, boxes: Iterable[SelectionBox]) -> SelectionGroup:
72
+ """
73
+ Add an iterable of :class:`SelectionBox` classes to this :class:`SelectionGroup`.
74
+
75
+ Note this will construct a new :class:`SelectionGroup` because it is immutable so cannot be modified in place.
76
+
77
+ >>> group1 = SelectionGroup(SelectionBox((-1, -1, -1), (0, 0, 0)))
78
+ >>> group2 = SelectionGroup([
79
+ >>> SelectionBox((0, 0, 0), (1, 1, 1)),
80
+ >>> SelectionBox((1, 1, 1), (2, 2, 2))
81
+ >>> ])
82
+ >>> group1 + group2
83
+ SelectionGroup([SelectionBox((-1, -1, -1), (0, 0, 0)), SelectionBox((0, 0, 0), (1, 1, 1)), SelectionBox((1, 1, 1), (2, 2, 2))])
84
+ >>> group1 += group2
85
+ >>> group1
86
+ SelectionGroup([SelectionBox((-1, -1, -1), (0, 0, 0)), SelectionBox((0, 0, 0), (1, 1, 1)), SelectionBox((1, 1, 1), (2, 2, 2))])
87
+
88
+ :param boxes: An iterable of boxes to add to this group.
89
+ :return: A new :class:`SelectionGroup` class containing the boxes from this instance and those in ``boxes``.
90
+ """
91
+ try:
92
+ boxes = tuple(boxes)
93
+ except:
94
+ return NotImplemented
95
+ if not all(isinstance(b, SelectionBox) for b in boxes):
96
+ return NotImplemented
97
+ return SelectionGroup(tuple(self) + boxes)
98
+
99
+ def __iter__(self) -> Iterator[SelectionBox]:
100
+ """An iterable of all the :class:`SelectionBox` classes in the group."""
101
+ yield from self._selection_boxes
102
+
103
+ def __len__(self) -> int:
104
+ """The number of :class:`SelectionBox` classes in the group."""
105
+ return len(self._selection_boxes)
106
+
107
+ def contains_block(self, x: int, y: int, z: int) -> bool:
108
+ return any(box.contains_block(x, y, z) for box in self._selection_boxes)
109
+
110
+ def contains_point(self, x: float, y: float, z: float) -> bool:
111
+ return any(box.contains_point(x, y, z) for box in self._selection_boxes)
112
+
113
+ @property
114
+ def blocks(self) -> Iterator[BlockCoordinates]:
115
+ """
116
+ The location of every block in the selection.
117
+
118
+ >>> group: SelectionGroup
119
+ >>> for x, y, z in group.blocks:
120
+ >>> ...
121
+
122
+ Note: if boxes intersect, the blocks in the intersected region will be included multiple times.
123
+
124
+ If this behaviour is not desired the :meth:`merge_boxes` method will return a new SelectionGroup with no intersections.
125
+
126
+ >>> for x, y, z in group.merge_boxes().blocks:
127
+ >>> ...
128
+
129
+ :return: An iterable of block locations.
130
+ """
131
+ for box in self.selection_boxes:
132
+ for coord in box.blocks:
133
+ yield coord
134
+
135
+ def __bool__(self) -> bool:
136
+ """Are there any boxes in the group."""
137
+ return bool(self._selection_boxes)
138
+
139
+ @overload
140
+ def __getitem__(self, item: int) -> SelectionBox: ...
141
+
142
+ @overload
143
+ def __getitem__(self, item: slice) -> SelectionGroup: ...
144
+
145
+ def __getitem__(self, item: int | slice) -> SelectionBox | SelectionGroup:
146
+ """Get the selection box at the given index."""
147
+ val = self._selection_boxes[item]
148
+ if isinstance(val, tuple):
149
+ return SelectionGroup(val)
150
+ else:
151
+ return val
152
+
153
+ @property
154
+ def min(self) -> BlockCoordinates:
155
+ """The minimum point of all the boxes in the group."""
156
+ return tuple(self.min_array.tolist())
157
+
158
+ @property
159
+ def min_array(self) -> numpy.ndarray:
160
+ """The minimum point of all the boxes in the group as a numpy array."""
161
+ if self._selection_boxes:
162
+ return numpy.min(numpy.array([box.min for box in self._selection_boxes]), 0) # type: ignore
163
+ else:
164
+ raise ValueError("SelectionGroup does not contain any SelectionBoxes")
165
+
166
+ @property
167
+ def min_x(self) -> int:
168
+ return int(self.min_array[0])
169
+
170
+ @property
171
+ def min_y(self) -> int:
172
+ return int(self.min_array[1])
173
+
174
+ @property
175
+ def min_z(self) -> int:
176
+ return int(self.min_array[2])
177
+
178
+ @property
179
+ def max(self) -> BlockCoordinates:
180
+ """The maximum point of all the boxes in the group."""
181
+ return tuple(self.max_array.tolist())
182
+
183
+ @property
184
+ def max_array(self) -> numpy.ndarray:
185
+ """The maximum point of all the boxes in the group as a numpy array."""
186
+ if self._selection_boxes:
187
+ return numpy.max(numpy.array([box.max for box in self._selection_boxes]), 0) # type: ignore
188
+ else:
189
+ raise ValueError("SelectionGroup does not contain any SelectionBoxes")
190
+
191
+ @property
192
+ def max_x(self) -> int:
193
+ return int(self.max_array[0])
194
+
195
+ @property
196
+ def max_y(self) -> int:
197
+ return int(self.max_array[1])
198
+
199
+ @property
200
+ def max_z(self) -> int:
201
+ return int(self.max_array[2])
202
+
203
+ @property
204
+ def bounds(self) -> tuple[BlockCoordinates, BlockCoordinates]:
205
+ return self.min, self.max
206
+
207
+ @property
208
+ def bounds_array(self) -> numpy.ndarray:
209
+ return numpy.array([self.min_array, self.max_array])
210
+
211
+ def to_box(self) -> SelectionBox:
212
+ """Create a `SelectionBox` based off the bounds of the boxes in the group."""
213
+ warnings.warn(
214
+ "to_box is depceciated. Use bounding_box instead.", DeprecationWarning
215
+ )
216
+ return self.bounding_box()
217
+
218
+ def bounding_box(self) -> SelectionBox:
219
+ return SelectionBox(self.min, self.max)
220
+
221
+ def selection_group(self) -> SelectionGroup:
222
+ return self
223
+
224
+ def merge_boxes(self) -> SelectionGroup:
225
+ """
226
+ Take the boxes as they were given to this class, merge neighbouring boxes and remove overlapping regions.
227
+
228
+ The result should be a SelectionGroup containing one or more SelectionBox classes that represents the same
229
+ volume as the original but with no overlapping boxes.
230
+ """
231
+ # remove duplicates
232
+ selection_boxes: list[SelectionBox] = []
233
+ for box in self.selection_boxes:
234
+ if not any(box == box_ for box_ in selection_boxes):
235
+ selection_boxes.append(box)
236
+
237
+ if len(selection_boxes) >= 2:
238
+ merge_boxes = True
239
+ while merge_boxes:
240
+ # find two neighbouring boxes and merge them
241
+ merge_boxes = False # if two boxes get merged this will be set back to True and this will run again.
242
+ box_index = 0 # the index of the first box
243
+ while box_index < len(selection_boxes):
244
+ box = selection_boxes[box_index]
245
+ other_index = box_index + 1 # the index of the second box.
246
+ # This always starts at one greater than box_index because
247
+ # the lower values were already checked the other way around
248
+ while other_index < len(selection_boxes):
249
+ other = selection_boxes[other_index]
250
+ x_dim = box.min_x == other.min_x and box.max_x == other.max_x
251
+ y_dim = box.min_y == other.min_y and box.max_y == other.max_y
252
+ z_dim = box.min_z == other.min_z and box.max_z == other.max_z
253
+
254
+ x_border = box.max_x == other.min_x or other.max_x == box.min_x
255
+ y_border = box.max_y == other.min_y or other.max_y == box.min_y
256
+ z_border = box.max_z == other.min_z or other.max_z == box.min_z
257
+
258
+ if (
259
+ (x_dim and y_dim and z_border)
260
+ or (x_dim and z_dim and y_border)
261
+ or (y_dim and z_dim and x_border)
262
+ ):
263
+ selection_boxes.pop(other_index)
264
+ selection_boxes.pop(box_index)
265
+ selection_boxes.append(
266
+ SelectionBox(
267
+ numpy.min([box.min, other.min], 0),
268
+ numpy.max([box.max, other.max], 0),
269
+ )
270
+ )
271
+ merge_boxes = True
272
+ box = selection_boxes[box_index]
273
+ other_index = box_index + 1
274
+ else:
275
+ other_index += 1
276
+ box_index += 1
277
+ return SelectionGroup(selection_boxes)
278
+
279
+ @property
280
+ def is_contiguous(self) -> bool:
281
+ """
282
+ Does the SelectionGroup represent one connected region (True) or multiple separated regions (False).
283
+
284
+ If two boxes are touching at the corners this is classed as contiguous.
285
+ """
286
+ # TODO: This needs some work. It will only work if the selections touch in a chain
287
+ # it does not care if it loops back and intersects itself. Does intersecting count as being contiguous?
288
+ # I would say yes
289
+ if len(self._selection_boxes) == 1:
290
+ return True
291
+
292
+ for i in range(len(self._selection_boxes) - 1):
293
+ sub_box: SelectionBox = self._selection_boxes[i]
294
+ next_box: SelectionBox = self._selection_boxes[i + 1]
295
+ if not sub_box.touches(next_box):
296
+ return False
297
+
298
+ return True
299
+
300
+ @property
301
+ def is_rectangular(self) -> bool:
302
+ """
303
+ Checks if the SelectionGroup is a rectangle
304
+
305
+ :return: True is the selection is a rectangle, False otherwise
306
+ """
307
+ return (
308
+ len(self._selection_boxes) == 1
309
+ or len(self.merge_boxes().selection_boxes) == 1
310
+ )
311
+
312
+ @property
313
+ def selection_boxes(self) -> tuple[SelectionBox, ...]:
314
+ """
315
+ A tuple of the :class:`SelectionBox` instances stored for this group.
316
+ """
317
+ return self._selection_boxes
318
+
319
+ @property
320
+ def selection_boxes_sorted(self) -> list[SelectionBox]:
321
+ """
322
+ A list of the :class:`SelectionBox` instances for this group sorted by their hash.
323
+ """
324
+ return sorted(self._selection_boxes, key=hash)
325
+
326
+ def chunk_count(self, sub_chunk_size: int = 16) -> int:
327
+ return len(self.chunk_locations(sub_chunk_size))
328
+
329
+ def chunk_locations(self, sub_chunk_size: int = 16) -> set[ChunkCoordinates]:
330
+ return set(
331
+ location
332
+ for box in self.selection_boxes
333
+ for location in box.chunk_locations(sub_chunk_size)
334
+ )
335
+
336
+ def chunk_boxes(
337
+ self, sub_chunk_size: int = 16
338
+ ) -> Iterator[tuple[ChunkCoordinates, SelectionBox]]:
339
+ for box in self.selection_boxes:
340
+ yield from box.chunk_boxes(sub_chunk_size)
341
+
342
+ def sub_chunk_count(self, sub_chunk_size: int = 16) -> int:
343
+ return len(self.sub_chunk_locations(sub_chunk_size))
344
+
345
+ def sub_chunk_locations(self, sub_chunk_size: int = 16) -> set[SubChunkCoordinates]:
346
+ return set(
347
+ location
348
+ for box in self.selection_boxes
349
+ for location in box.sub_chunk_locations(sub_chunk_size)
350
+ )
351
+
352
+ def sub_chunk_boxes(
353
+ self, sub_chunk_size: int = 16
354
+ ) -> Iterator[tuple[SubChunkCoordinates, SelectionBox]]:
355
+ for box in self.selection_boxes:
356
+ for (cx, cy, cz), sub_box in box.sub_chunk_boxes(sub_chunk_size):
357
+ yield (cx, cy, cz), box
358
+
359
+ def _intersects(self, other: AbstractBaseSelection) -> bool:
360
+ if isinstance(other, SelectionBox):
361
+ return any(self_box.intersects(other) for self_box in self.selection_boxes)
362
+ elif isinstance(other, SelectionGroup):
363
+ return any(
364
+ self_box.intersects(other_box)
365
+ for self_box in self.selection_boxes
366
+ for other_box in other.selection_boxes
367
+ )
368
+ return NotImplemented
369
+
370
+ def intersection(self, other: AbstractBaseSelection) -> SelectionGroup:
371
+ return self._intersection(other)
372
+
373
+ def _intersection(self, other: AbstractBaseSelection) -> SelectionGroup:
374
+ group = other.selection_group()
375
+ intersection: list[SelectionBox] = []
376
+ for self_box in self.selection_boxes:
377
+ for other_box in group:
378
+ if self_box.intersects(other_box):
379
+ intersection.append(self_box.intersection(other_box))
380
+ return SelectionGroup(intersection)
381
+
382
+ def subtract(self, other: AbstractBaseSelection) -> SelectionGroup:
383
+ """
384
+ Returns a new :class:`SelectionGroup` containing the volume that does not intersect with other.
385
+
386
+ This may be empty if other fully contains self or equal to self if they do not intersect.
387
+
388
+ :param other: The :class:`SelectionBox` or :class:`SelectionGroup` to subtract.
389
+ """
390
+ return self._subtract(other)
391
+
392
+ def _subtract(self, other: AbstractBaseSelection) -> SelectionGroup:
393
+ group = other.selection_group()
394
+ selections = self
395
+ for other_box in group:
396
+ # for each box in other
397
+ selections_new = SelectionGroup()
398
+ for self_box in selections:
399
+ selections_new += self_box.subtract(other_box)
400
+ selections = selections_new
401
+ if not selections:
402
+ break
403
+ return selections
404
+
405
+ def union(self, other: AbstractBaseSelection) -> SelectionGroup:
406
+ """
407
+ Returns a new SelectionGroup containing the volume of self and other.
408
+
409
+ :param other: The other selection to add to this one.
410
+ """
411
+ group = other.selection_group()
412
+ if group.is_subset(self):
413
+ return self
414
+ else:
415
+ return self.subtract(group) + group
416
+
417
+ def is_subset(self, other: AbstractBaseSelection) -> bool:
418
+ """
419
+ Is this selection completely contained within ``other``.
420
+
421
+ :param other: The other selection to test against.
422
+ :return: True if this selection completely fits in other.
423
+ """
424
+ return not self.subtract(other.selection_group())
425
+
426
+ def closest_vector_intersection(
427
+ self,
428
+ origin: PointCoordinates | PointCoordinatesArray,
429
+ direction: PointCoordinates | PointCoordinatesArray,
430
+ ) -> tuple[int | None, float]:
431
+ """
432
+ Returns the index for the closest box in the look vector and the multiplier of the look vector to get there.
433
+
434
+ :param origin: The origin of the vector
435
+ :param direction: The vector magnitude in x, y and z
436
+ :return: Index for the closest box and the multiplier of the vector to get there. None, inf if no intersection.
437
+ """
438
+ index_return = None
439
+ multiplier = float("inf")
440
+ for index, box in enumerate(self._selection_boxes):
441
+ mult = box.intersects_vector(origin, direction)
442
+ if mult is not None and mult < multiplier:
443
+ multiplier = mult
444
+ index_return = index
445
+ return index_return, multiplier
446
+
447
+ def transform(
448
+ self, scale: FloatTriplet, rotation: FloatTriplet, translation: FloatTriplet
449
+ ) -> SelectionGroup:
450
+ """
451
+ Creates a new :class:`SelectionGroup` transformed by the given inputs.
452
+
453
+ :param scale: A tuple of scaling factors in the x, y and z axis.
454
+ :param rotation: The rotation about the x, y and z axis in radians.
455
+ :param translation: The translation about the x, y and z axis.
456
+ :return: A new :class:`~amulet.api.selection.SelectionGroup` representing the transformed selection.
457
+ """
458
+ selection_group = SelectionGroup()
459
+ for selection in self.selection_boxes:
460
+ selection_group += selection.transform(scale, rotation, translation)
461
+ return selection_group
462
+
463
+ @property
464
+ def volume(self) -> int:
465
+ return sum(box.volume for box in self.selection_boxes)
466
+
467
+ @property
468
+ def footprint_area(self) -> int:
469
+ """
470
+ The 2D area that the selection fills when looking at the selection from above.
471
+ """
472
+ return (
473
+ SelectionGroup(
474
+ [
475
+ SelectionBox((box.min_x, 0, box.min_z), (box.max_x, 1, box.max_z))
476
+ for box in self.selection_boxes
477
+ ]
478
+ )
479
+ .merge_boxes()
480
+ .volume
481
+ )
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ from . import (
4
+ call_spec,
5
+ matrix,
6
+ numpy,
7
+ shareable_lock,
8
+ signal,
9
+ task_manager,
10
+ typing,
11
+ weakref,
12
+ world_utils,
13
+ )
14
+
15
+ __all__ = [
16
+ "call_spec",
17
+ "matrix",
18
+ "numpy",
19
+ "shareable_lock",
20
+ "signal",
21
+ "task_manager",
22
+ "typing",
23
+ "weakref",
24
+ "world_utils",
25
+ ]
26
+
27
+ def __dir__() -> typing.Any: ...
28
+ def __getattr__(arg0: typing.Any) -> typing.Any: ...
@@ -0,0 +1,24 @@
1
+ from ._call_spec import (
2
+ AbstractArg,
3
+ AbstractHashableArg,
4
+ ConstantArg,
5
+ StringArg,
6
+ FilePathArg,
7
+ DirectoryPathArg,
8
+ BytesArg,
9
+ BoolArg,
10
+ IntArg,
11
+ FloatArg,
12
+ TupleArg,
13
+ HashableTupleArg,
14
+ SequenceArg,
15
+ DictArg,
16
+ UnionArg,
17
+ HashableUnionArg,
18
+ CallableArg,
19
+ HashableCallableArg,
20
+ PositionalArgs,
21
+ CallSpec,
22
+ callable_spec,
23
+ method_spec,
24
+ )
@@ -0,0 +1,53 @@
1
+ from __future__ import annotations
2
+
3
+ from amulet.utils.call_spec._call_spec import (
4
+ AbstractArg,
5
+ AbstractHashableArg,
6
+ BoolArg,
7
+ BytesArg,
8
+ CallableArg,
9
+ CallSpec,
10
+ ConstantArg,
11
+ DictArg,
12
+ DirectoryPathArg,
13
+ FilePathArg,
14
+ FloatArg,
15
+ HashableCallableArg,
16
+ HashableTupleArg,
17
+ HashableUnionArg,
18
+ IntArg,
19
+ PositionalArgs,
20
+ SequenceArg,
21
+ StringArg,
22
+ TupleArg,
23
+ UnionArg,
24
+ callable_spec,
25
+ method_spec,
26
+ )
27
+
28
+ from . import _call_spec
29
+
30
+ __all__ = [
31
+ "AbstractArg",
32
+ "AbstractHashableArg",
33
+ "BoolArg",
34
+ "BytesArg",
35
+ "CallSpec",
36
+ "CallableArg",
37
+ "ConstantArg",
38
+ "DictArg",
39
+ "DirectoryPathArg",
40
+ "FilePathArg",
41
+ "FloatArg",
42
+ "HashableCallableArg",
43
+ "HashableTupleArg",
44
+ "HashableUnionArg",
45
+ "IntArg",
46
+ "PositionalArgs",
47
+ "SequenceArg",
48
+ "StringArg",
49
+ "TupleArg",
50
+ "UnionArg",
51
+ "callable_spec",
52
+ "method_spec",
53
+ ]