amulet-core 1.9.19__py3-none-any.whl → 1.9.20__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 amulet-core might be problematic. Click here for more details.

Files changed (198) hide show
  1. amulet/__init__.py +27 -27
  2. amulet/__pyinstaller/__init__.py +2 -2
  3. amulet/__pyinstaller/hook-amulet.py +4 -4
  4. amulet/_version.py +21 -21
  5. amulet/api/__init__.py +2 -2
  6. amulet/api/abstract_base_entity.py +128 -128
  7. amulet/api/block.py +630 -630
  8. amulet/api/block_entity.py +71 -71
  9. amulet/api/cache.py +107 -107
  10. amulet/api/chunk/__init__.py +6 -6
  11. amulet/api/chunk/biomes.py +207 -207
  12. amulet/api/chunk/block_entity_dict.py +175 -175
  13. amulet/api/chunk/blocks.py +46 -46
  14. amulet/api/chunk/chunk.py +389 -389
  15. amulet/api/chunk/entity_list.py +75 -75
  16. amulet/api/chunk/status.py +167 -167
  17. amulet/api/data_types/__init__.py +4 -4
  18. amulet/api/data_types/generic_types.py +4 -4
  19. amulet/api/data_types/operation_types.py +16 -16
  20. amulet/api/data_types/world_types.py +49 -49
  21. amulet/api/data_types/wrapper_types.py +71 -71
  22. amulet/api/entity.py +74 -74
  23. amulet/api/errors.py +119 -119
  24. amulet/api/history/__init__.py +36 -36
  25. amulet/api/history/base/__init__.py +3 -3
  26. amulet/api/history/base/base_history.py +26 -26
  27. amulet/api/history/base/history_manager.py +63 -63
  28. amulet/api/history/base/revision_manager.py +73 -73
  29. amulet/api/history/changeable.py +15 -15
  30. amulet/api/history/data_types.py +7 -7
  31. amulet/api/history/history_manager/__init__.py +3 -3
  32. amulet/api/history/history_manager/container.py +102 -102
  33. amulet/api/history/history_manager/database.py +279 -279
  34. amulet/api/history/history_manager/meta.py +93 -93
  35. amulet/api/history/history_manager/object.py +116 -116
  36. amulet/api/history/revision_manager/__init__.py +2 -2
  37. amulet/api/history/revision_manager/disk.py +33 -33
  38. amulet/api/history/revision_manager/ram.py +12 -12
  39. amulet/api/item.py +75 -75
  40. amulet/api/level/__init__.py +4 -4
  41. amulet/api/level/base_level/__init__.py +1 -1
  42. amulet/api/level/base_level/base_level.py +1035 -1026
  43. amulet/api/level/base_level/chunk_manager.py +227 -227
  44. amulet/api/level/base_level/clone.py +389 -389
  45. amulet/api/level/base_level/player_manager.py +101 -101
  46. amulet/api/level/immutable_structure/__init__.py +1 -1
  47. amulet/api/level/immutable_structure/immutable_structure.py +94 -94
  48. amulet/api/level/immutable_structure/void_format_wrapper.py +117 -117
  49. amulet/api/level/structure.py +22 -22
  50. amulet/api/level/world.py +19 -19
  51. amulet/api/partial_3d_array/__init__.py +2 -2
  52. amulet/api/partial_3d_array/base_partial_3d_array.py +263 -263
  53. amulet/api/partial_3d_array/bounded_partial_3d_array.py +528 -528
  54. amulet/api/partial_3d_array/data_types.py +15 -15
  55. amulet/api/partial_3d_array/unbounded_partial_3d_array.py +229 -229
  56. amulet/api/partial_3d_array/util.py +152 -152
  57. amulet/api/player.py +65 -65
  58. amulet/api/registry/__init__.py +2 -2
  59. amulet/api/registry/base_registry.py +34 -34
  60. amulet/api/registry/biome_manager.py +153 -153
  61. amulet/api/registry/block_manager.py +156 -156
  62. amulet/api/selection/__init__.py +2 -2
  63. amulet/api/selection/abstract_selection.py +315 -315
  64. amulet/api/selection/box.py +805 -805
  65. amulet/api/selection/group.py +488 -488
  66. amulet/api/structure.py +37 -37
  67. amulet/api/wrapper/__init__.py +8 -8
  68. amulet/api/wrapper/chunk/interface.py +441 -441
  69. amulet/api/wrapper/chunk/translator.py +567 -567
  70. amulet/api/wrapper/format_wrapper.py +772 -772
  71. amulet/api/wrapper/structure_format_wrapper.py +116 -116
  72. amulet/api/wrapper/world_format_wrapper.py +63 -63
  73. amulet/level/__init__.py +1 -1
  74. amulet/level/formats/anvil_forge_world.py +40 -40
  75. amulet/level/formats/anvil_world/__init__.py +3 -3
  76. amulet/level/formats/anvil_world/_sector_manager.py +291 -384
  77. amulet/level/formats/anvil_world/data_pack/__init__.py +2 -2
  78. amulet/level/formats/anvil_world/data_pack/data_pack.py +224 -224
  79. amulet/level/formats/anvil_world/data_pack/data_pack_manager.py +77 -77
  80. amulet/level/formats/anvil_world/dimension.py +177 -177
  81. amulet/level/formats/anvil_world/format.py +769 -769
  82. amulet/level/formats/anvil_world/region.py +384 -384
  83. amulet/level/formats/construction/__init__.py +3 -3
  84. amulet/level/formats/construction/format_wrapper.py +515 -515
  85. amulet/level/formats/construction/interface.py +134 -134
  86. amulet/level/formats/construction/section.py +60 -60
  87. amulet/level/formats/construction/util.py +165 -165
  88. amulet/level/formats/leveldb_world/__init__.py +3 -3
  89. amulet/level/formats/leveldb_world/chunk.py +33 -33
  90. amulet/level/formats/leveldb_world/dimension.py +385 -419
  91. amulet/level/formats/leveldb_world/format.py +659 -641
  92. amulet/level/formats/leveldb_world/interface/chunk/__init__.py +36 -36
  93. amulet/level/formats/leveldb_world/interface/chunk/base_leveldb_interface.py +836 -836
  94. amulet/level/formats/leveldb_world/interface/chunk/generate_interface.py +31 -31
  95. amulet/level/formats/leveldb_world/interface/chunk/leveldb_0.py +30 -30
  96. amulet/level/formats/leveldb_world/interface/chunk/leveldb_1.py +12 -12
  97. amulet/level/formats/leveldb_world/interface/chunk/leveldb_10.py +12 -12
  98. amulet/level/formats/leveldb_world/interface/chunk/leveldb_11.py +12 -12
  99. amulet/level/formats/leveldb_world/interface/chunk/leveldb_12.py +12 -12
  100. amulet/level/formats/leveldb_world/interface/chunk/leveldb_13.py +12 -12
  101. amulet/level/formats/leveldb_world/interface/chunk/leveldb_14.py +12 -12
  102. amulet/level/formats/leveldb_world/interface/chunk/leveldb_15.py +12 -12
  103. amulet/level/formats/leveldb_world/interface/chunk/leveldb_16.py +12 -12
  104. amulet/level/formats/leveldb_world/interface/chunk/leveldb_17.py +12 -12
  105. amulet/level/formats/leveldb_world/interface/chunk/leveldb_18.py +12 -12
  106. amulet/level/formats/leveldb_world/interface/chunk/leveldb_19.py +12 -12
  107. amulet/level/formats/leveldb_world/interface/chunk/leveldb_2.py +12 -12
  108. amulet/level/formats/leveldb_world/interface/chunk/leveldb_20.py +12 -12
  109. amulet/level/formats/leveldb_world/interface/chunk/leveldb_21.py +12 -12
  110. amulet/level/formats/leveldb_world/interface/chunk/leveldb_22.py +12 -12
  111. amulet/level/formats/leveldb_world/interface/chunk/leveldb_23.py +10 -10
  112. amulet/level/formats/leveldb_world/interface/chunk/leveldb_24.py +10 -10
  113. amulet/level/formats/leveldb_world/interface/chunk/leveldb_25.py +24 -24
  114. amulet/level/formats/leveldb_world/interface/chunk/leveldb_26.py +10 -10
  115. amulet/level/formats/leveldb_world/interface/chunk/leveldb_27.py +10 -10
  116. amulet/level/formats/leveldb_world/interface/chunk/leveldb_28.py +10 -10
  117. amulet/level/formats/leveldb_world/interface/chunk/leveldb_29.py +33 -33
  118. amulet/level/formats/leveldb_world/interface/chunk/leveldb_3.py +57 -57
  119. amulet/level/formats/leveldb_world/interface/chunk/leveldb_30.py +10 -10
  120. amulet/level/formats/leveldb_world/interface/chunk/leveldb_31.py +10 -10
  121. amulet/level/formats/leveldb_world/interface/chunk/leveldb_32.py +10 -10
  122. amulet/level/formats/leveldb_world/interface/chunk/leveldb_33.py +10 -10
  123. amulet/level/formats/leveldb_world/interface/chunk/leveldb_34.py +10 -10
  124. amulet/level/formats/leveldb_world/interface/chunk/leveldb_35.py +10 -10
  125. amulet/level/formats/leveldb_world/interface/chunk/leveldb_36.py +10 -10
  126. amulet/level/formats/leveldb_world/interface/chunk/leveldb_37.py +10 -10
  127. amulet/level/formats/leveldb_world/interface/chunk/leveldb_38.py +10 -10
  128. amulet/level/formats/leveldb_world/interface/chunk/leveldb_39.py +12 -12
  129. amulet/level/formats/leveldb_world/interface/chunk/leveldb_4.py +12 -12
  130. amulet/level/formats/leveldb_world/interface/chunk/leveldb_40.py +16 -16
  131. amulet/level/formats/leveldb_world/interface/chunk/leveldb_5.py +12 -12
  132. amulet/level/formats/leveldb_world/interface/chunk/leveldb_6.py +12 -12
  133. amulet/level/formats/leveldb_world/interface/chunk/leveldb_7.py +12 -12
  134. amulet/level/formats/leveldb_world/interface/chunk/leveldb_8.py +180 -180
  135. amulet/level/formats/leveldb_world/interface/chunk/leveldb_9.py +18 -18
  136. amulet/level/formats/leveldb_world/interface/chunk/leveldb_chunk_versions.py +79 -79
  137. amulet/level/formats/mcstructure/__init__.py +3 -3
  138. amulet/level/formats/mcstructure/chunk.py +50 -50
  139. amulet/level/formats/mcstructure/format_wrapper.py +408 -408
  140. amulet/level/formats/mcstructure/interface.py +175 -175
  141. amulet/level/formats/schematic/__init__.py +3 -3
  142. amulet/level/formats/schematic/chunk.py +55 -55
  143. amulet/level/formats/schematic/data_types.py +4 -4
  144. amulet/level/formats/schematic/format_wrapper.py +373 -373
  145. amulet/level/formats/schematic/interface.py +142 -142
  146. amulet/level/formats/sponge_schem/__init__.py +4 -4
  147. amulet/level/formats/sponge_schem/chunk.py +62 -62
  148. amulet/level/formats/sponge_schem/format_wrapper.py +463 -463
  149. amulet/level/formats/sponge_schem/interface.py +118 -118
  150. amulet/level/formats/sponge_schem/varint/__init__.py +1 -1
  151. amulet/level/formats/sponge_schem/varint/varint.py +87 -87
  152. amulet/level/interfaces/chunk/anvil/anvil_0.py +72 -72
  153. amulet/level/interfaces/chunk/anvil/anvil_1444.py +336 -336
  154. amulet/level/interfaces/chunk/anvil/anvil_1466.py +94 -94
  155. amulet/level/interfaces/chunk/anvil/anvil_1467.py +37 -37
  156. amulet/level/interfaces/chunk/anvil/anvil_1484.py +20 -20
  157. amulet/level/interfaces/chunk/anvil/anvil_1503.py +20 -20
  158. amulet/level/interfaces/chunk/anvil/anvil_1519.py +34 -34
  159. amulet/level/interfaces/chunk/anvil/anvil_1901.py +20 -20
  160. amulet/level/interfaces/chunk/anvil/anvil_1908.py +20 -20
  161. amulet/level/interfaces/chunk/anvil/anvil_1912.py +21 -21
  162. amulet/level/interfaces/chunk/anvil/anvil_1934.py +20 -20
  163. amulet/level/interfaces/chunk/anvil/anvil_2203.py +69 -69
  164. amulet/level/interfaces/chunk/anvil/anvil_2529.py +19 -19
  165. amulet/level/interfaces/chunk/anvil/anvil_2681.py +76 -76
  166. amulet/level/interfaces/chunk/anvil/anvil_2709.py +19 -19
  167. amulet/level/interfaces/chunk/anvil/anvil_2844.py +267 -267
  168. amulet/level/interfaces/chunk/anvil/anvil_3463.py +19 -19
  169. amulet/level/interfaces/chunk/anvil/anvil_na.py +607 -607
  170. amulet/level/interfaces/chunk/anvil/base_anvil_interface.py +326 -326
  171. amulet/level/load.py +59 -59
  172. amulet/level/loader.py +95 -95
  173. amulet/level/translators/chunk/bedrock/__init__.py +267 -267
  174. amulet/level/translators/chunk/bedrock/bedrock_nbt_blockstate_translator.py +46 -46
  175. amulet/level/translators/chunk/bedrock/bedrock_numerical_translator.py +39 -39
  176. amulet/level/translators/chunk/bedrock/bedrock_psudo_numerical_translator.py +37 -37
  177. amulet/level/translators/chunk/java/java_1_18_translator.py +40 -40
  178. amulet/level/translators/chunk/java/java_blockstate_translator.py +94 -94
  179. amulet/level/translators/chunk/java/java_numerical_translator.py +62 -62
  180. amulet/libs/leveldb/__init__.py +7 -7
  181. amulet/operations/__init__.py +5 -5
  182. amulet/operations/clone.py +18 -18
  183. amulet/operations/delete_chunk.py +32 -32
  184. amulet/operations/fill.py +30 -30
  185. amulet/operations/paste.py +65 -65
  186. amulet/operations/replace.py +58 -58
  187. amulet/utils/__init__.py +14 -14
  188. amulet/utils/format_utils.py +41 -41
  189. amulet/utils/generator.py +15 -15
  190. amulet/utils/matrix.py +243 -243
  191. amulet/utils/numpy_helpers.py +46 -46
  192. amulet/utils/world_utils.py +349 -349
  193. {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/METADATA +97 -97
  194. amulet_core-1.9.20.dist-info/RECORD +208 -0
  195. amulet_core-1.9.19.dist-info/RECORD +0 -208
  196. {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/WHEEL +0 -0
  197. {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/entry_points.txt +0 -0
  198. {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/top_level.txt +0 -0
@@ -1,389 +1,389 @@
1
- import sys
2
- from typing import TYPE_CHECKING, Tuple, Generator, Optional
3
- import numpy
4
- import os
5
-
6
- from amulet.api.data_types import Dimension, BlockCoordinates, FloatTriplet
7
- from amulet.api.selection import SelectionGroup, SelectionBox
8
- from amulet.api.block import Block, UniversalAirBlock
9
- from amulet.api.errors import ChunkDoesNotExist, ChunkLoadError
10
- from amulet.api.chunk import Chunk
11
- from amulet.api.registry import BlockManager
12
- from amulet.utils.matrix import transform_matrix, displacement_matrix
13
- import amulet.api.level
14
-
15
- if TYPE_CHECKING:
16
- from .base_level import BaseLevel
17
-
18
-
19
- def is_sub_block(skip_blocks: Tuple[Block, ...], b: Block) -> bool:
20
- """Is the Block `b` a sub-block of any block in skip_blocks."""
21
- for skip_block in skip_blocks:
22
- if skip_block.namespaced_name == b.namespaced_name:
23
- other_properties = b.properties
24
- if skip_block.properties == {
25
- key: other_properties[key] for key in skip_block.properties.keys()
26
- }:
27
- return True
28
- return False
29
-
30
-
31
- def gen_paste_blocks(
32
- block_palette: BlockManager, skip_blocks: Tuple[Block, ...]
33
- ) -> numpy.ndarray:
34
- """Create a numpy bool array of all the blocks which should be pasted.
35
-
36
- :param block_palette: The block palette of the chunk.
37
- :param skip_blocks: Blocks to not copy. If a property is not defined it will match any value.
38
- :return: Bool array of which blocks to copy.
39
- """
40
- return numpy.invert(
41
- numpy.vectorize(lambda b: is_sub_block(skip_blocks, b))(block_palette.blocks)
42
- )
43
-
44
-
45
- def clone(
46
- src_structure: "BaseLevel",
47
- src_dimension: Dimension,
48
- src_selection: SelectionGroup,
49
- dst_structure: "BaseLevel",
50
- dst_dimension: Dimension,
51
- dst_selection_bounds: SelectionGroup,
52
- location: BlockCoordinates,
53
- scale: FloatTriplet = (1.0, 1.0, 1.0),
54
- rotation: FloatTriplet = (0.0, 0.0, 0.0),
55
- include_blocks: bool = True,
56
- include_entities: bool = True,
57
- skip_blocks: Tuple[Block, ...] = (),
58
- copy_chunk_not_exist: bool = False,
59
- ) -> Generator[float, None, None]:
60
- """Clone the source object data into the destination object with an optional transform.
61
- The src and dst can be the same object.
62
- Note this command may change in the future. Refer to all keyword arguments via the keyword.
63
- :param src_structure: The source structure to paste into the destination structure.
64
- :param src_dimension: The dimension of the source structure to use.
65
- :param src_selection: The area of the source structure to copy.
66
- :param dst_structure: The destination structure to paste into.
67
- :param dst_dimension: The dimension of the destination structure to use.
68
- :param dst_selection_bounds: The area of the destination structure that can be modified.
69
- :param location: The location where the centre of the `src_structure` will be in the `dst_structure`
70
- :param scale: The scale in the x, y and z axis. These can be negative to mirror.
71
- :param rotation: The rotation in degrees around each of the axis.
72
- :param include_blocks: Include blocks from the `src_structure`.
73
- :param include_entities: Include entities from the `src_structure`.
74
- :param skip_blocks: If a block matches a block in this list it will not be copied.
75
- :param copy_chunk_not_exist: If a chunk does not exist in the source should it be copied over as air. Always False where `src_structure` is a World.
76
- :return: A generator of floats from 0 to 1 with the progress of the paste operation.
77
- """
78
- location = tuple(location)
79
- src_selection = src_selection.merge_boxes()
80
- if include_blocks or include_entities:
81
- # we actually have to do something
82
- if isinstance(src_structure, amulet.api.level.World):
83
- copy_chunk_not_exist = False
84
-
85
- # TODO: look into if this can be a float so it will always be the exact middle
86
- rotation_point: numpy.ndarray = (
87
- (src_selection.max_array + src_selection.min_array) // 2
88
- ).astype(int)
89
-
90
- if src_structure is dst_structure and src_dimension == dst_dimension:
91
- # copying from an object to itself in the same dimension.
92
- # if the selections do not overlap this can be achieved directly
93
- # if they do overlap the selection will first need extracting
94
- # TODO: implement the above
95
- if (
96
- tuple(rotation_point) == location
97
- and scale == (1.0, 1.0, 1.0)
98
- and rotation == (0.0, 0.0, 0.0)
99
- ):
100
- # The src_object was pasted into itself at the same location. Nothing will change so do nothing.
101
- return
102
- src_structure = src_structure.extract_structure(
103
- src_selection, src_dimension
104
- )
105
- src_dimension = src_structure.dimensions[0]
106
-
107
- src_structure: "BaseLevel"
108
-
109
- # TODO: I don't know if this is feasible for large boxes: get the intersection of the source and destination selections and iterate over that to minimise work
110
- if any(rotation) or any(s != 1 for s in scale):
111
- # if the selection needs transforming
112
- rotation_radians = tuple(numpy.radians(rotation))
113
- transform = numpy.matmul(
114
- transform_matrix(scale, rotation_radians, location),
115
- displacement_matrix(*-rotation_point),
116
- )
117
-
118
- last_src: Optional[Tuple[int, int]] = None
119
- src_chunk: Optional[
120
- Chunk
121
- ] = None # None here means the chunk does not exist or failed to load. Treat it as if it was air.
122
- last_dst: Optional[Tuple[int, int]] = None
123
- dst_chunk: Optional[
124
- Chunk
125
- ] = None # None here means the chunk failed to load. Do not modify it.
126
-
127
- sum_progress = 0
128
- volumes = tuple(
129
- box.sub_chunk_count() for box in src_selection.selection_boxes
130
- )
131
- sum_volumes = sum(volumes)
132
- volumes = tuple(vol / sum_volumes for vol in volumes)
133
-
134
- if include_blocks:
135
- blocks_to_skip = set(skip_blocks)
136
- for box_index, box in enumerate(src_selection.selection_boxes):
137
- for progress, src_coords, dst_coords in box.transformed_points(
138
- transform
139
- ):
140
- if src_coords is not None:
141
- dst_cx, dst_cy, dst_cz = dst_coords[0] >> 4
142
- if (dst_cx, dst_cz) != last_dst:
143
- last_dst = dst_cx, dst_cz
144
- try:
145
- dst_chunk = dst_structure.get_chunk(
146
- dst_cx, dst_cz, dst_dimension
147
- )
148
- except ChunkDoesNotExist:
149
- dst_chunk = dst_structure.create_chunk(
150
- dst_cx, dst_cz, dst_dimension
151
- )
152
- except ChunkLoadError:
153
- dst_chunk = None
154
-
155
- src_coords = numpy.floor(src_coords).astype(int)
156
- # due to how the coords are found dst_coords will all be in the same sub-chunk
157
- src_chunk_coords = src_coords >> 4
158
-
159
- # split the src coords into which sub-chunks they came from
160
- unique_chunks, inverse, counts = numpy.unique(
161
- src_chunk_coords,
162
- return_inverse=True,
163
- return_counts=True,
164
- axis=0,
165
- )
166
- chunk_indexes = numpy.argsort(inverse)
167
- src_block_locations = numpy.split(
168
- src_coords[chunk_indexes], numpy.cumsum(counts)[:-1]
169
- )
170
- dst_block_locations = numpy.split(
171
- dst_coords[chunk_indexes], numpy.cumsum(counts)[:-1]
172
- )
173
- for chunk_location, src_blocks, dst_blocks in zip(
174
- unique_chunks, src_block_locations, dst_block_locations
175
- ):
176
- # for each src sub-chunk
177
- src_cx, src_cy, src_cz = chunk_location
178
- if (src_cx, src_cz) != last_src:
179
- last_src = src_cx, src_cz
180
- try:
181
- src_chunk = src_structure.get_chunk(
182
- src_cx, src_cz, src_dimension
183
- )
184
- except ChunkLoadError:
185
- src_chunk = None
186
-
187
- if dst_chunk is not None:
188
- if (
189
- src_chunk is not None
190
- and src_cy in src_chunk.blocks
191
- ):
192
- # TODO implement support for individual block rotation
193
- block_ids = src_chunk.blocks.get_sub_chunk(
194
- src_cy
195
- )[tuple(src_blocks.T % 16)]
196
-
197
- for block_id in numpy.unique(block_ids):
198
- block = src_chunk.block_palette[block_id]
199
- if not is_sub_block(skip_blocks, block):
200
- mask = block_ids == block_id
201
- dst_blocks_ = dst_blocks[mask]
202
-
203
- transformed_block = src_structure.translation_manager.transform_universal_block(
204
- block, transform
205
- )
206
-
207
- dst_chunk.blocks.get_sub_chunk(dst_cy)[
208
- tuple(dst_blocks_.T % 16)
209
- ] = dst_chunk.block_palette.get_add_block(
210
- transformed_block
211
- )
212
-
213
- src_blocks_ = src_blocks[mask]
214
- for src_location, dst_location in zip(
215
- src_blocks_, dst_blocks_
216
- ):
217
- src_location = tuple(
218
- src_location.tolist()
219
- )
220
- dst_location = tuple(
221
- dst_location.tolist()
222
- )
223
-
224
- if (
225
- src_location
226
- in src_chunk.block_entities
227
- ):
228
- dst_chunk.block_entities[
229
- dst_location
230
- ] = src_chunk.block_entities[
231
- src_location
232
- ].new_at_location(
233
- *dst_location
234
- )
235
- elif (
236
- dst_location
237
- in dst_chunk.block_entities
238
- ):
239
- del dst_chunk.block_entities[
240
- dst_location
241
- ]
242
-
243
- dst_chunk.changed = True
244
- elif UniversalAirBlock not in blocks_to_skip:
245
- dst_chunk.blocks.get_sub_chunk(dst_cy)[
246
- tuple(dst_blocks.T % 16)
247
- ] = dst_chunk.block_palette.get_add_block(
248
- UniversalAirBlock
249
- )
250
- for location in dst_blocks:
251
- location = tuple(location.tolist())
252
- if location in dst_chunk.block_entities:
253
- del dst_chunk.block_entities[location]
254
- dst_chunk.changed = True
255
- yield sum_progress + volumes[box_index] * progress
256
- sum_progress += volumes[box_index]
257
-
258
- else:
259
- # the selection can be cloned as is
260
- # the transform from the structure location to the world location
261
- offset = numpy.asarray(location).astype(int) - rotation_point
262
- moved_min_location = src_selection.min_array + offset
263
-
264
- iter_count = len(
265
- list(
266
- src_structure.get_moved_coord_slice_box(
267
- src_dimension,
268
- moved_min_location,
269
- src_selection,
270
- dst_structure.sub_chunk_size,
271
- yield_missing_chunks=copy_chunk_not_exist,
272
- )
273
- )
274
- )
275
-
276
- count = 0
277
-
278
- for (
279
- src_chunk,
280
- src_slices,
281
- src_box,
282
- (dst_cx, dst_cz),
283
- dst_slices,
284
- dst_box,
285
- ) in src_structure.get_moved_chunk_slice_box(
286
- src_dimension,
287
- moved_min_location,
288
- src_selection,
289
- dst_structure.sub_chunk_size,
290
- create_missing_chunks=copy_chunk_not_exist,
291
- ):
292
- src_chunk: Chunk
293
- src_slices: Tuple[slice, slice, slice]
294
- src_box: SelectionBox
295
- dst_cx: int
296
- dst_cz: int
297
- dst_slices: Tuple[slice, slice, slice]
298
- dst_box: SelectionBox
299
-
300
- # load the destination chunk
301
- try:
302
- dst_chunk = dst_structure.get_chunk(dst_cx, dst_cz, dst_dimension)
303
- except ChunkDoesNotExist:
304
- dst_chunk = dst_structure.create_chunk(
305
- dst_cx, dst_cz, dst_dimension
306
- )
307
- except ChunkLoadError:
308
- count += 1
309
- continue
310
-
311
- if include_blocks:
312
- # a boolean array specifying if each index should be pasted.
313
- paste_blocks = gen_paste_blocks(
314
- src_chunk.block_palette, skip_blocks
315
- )
316
-
317
- # create a look up table converting the source block ids to the destination block ids
318
- gab = numpy.vectorize(
319
- dst_chunk.block_palette.get_add_block, otypes=[numpy.uint32]
320
- )
321
- lut = gab(src_chunk.block_palette.blocks)
322
-
323
- # iterate through all block entities in the chunk and work out if the block is going to be overwritten
324
- remove_block_entities = []
325
- for block_entity_location in dst_chunk.block_entities.keys():
326
- if block_entity_location in dst_box:
327
- chunk_block_entity_location = (
328
- numpy.array(block_entity_location) - offset
329
- )
330
- chunk_block_entity_location[[0, 2]] %= 16
331
- if paste_blocks[
332
- src_chunk.blocks[tuple(chunk_block_entity_location)]
333
- ]:
334
- remove_block_entities.append(block_entity_location)
335
- for block_entity_location in remove_block_entities:
336
- del dst_chunk.block_entities[block_entity_location]
337
-
338
- # copy over the source block entities if the source block is supposed to be pasted
339
- for (
340
- block_entity_location,
341
- block_entity,
342
- ) in src_chunk.block_entities.items():
343
- if block_entity_location in src_box:
344
- chunk_block_entity_location = numpy.array(
345
- block_entity_location
346
- )
347
- chunk_block_entity_location[[0, 2]] %= 16
348
- if paste_blocks[
349
- src_chunk.blocks[tuple(chunk_block_entity_location)]
350
- ]:
351
- dst_chunk.block_entities.insert(
352
- block_entity.new_at_location(
353
- *offset + block_entity_location
354
- )
355
- )
356
-
357
- try:
358
- block_mask = src_chunk.blocks[src_slices]
359
- mask = paste_blocks[block_mask]
360
- dst_chunk.blocks[dst_slices][mask] = lut[
361
- src_chunk.blocks[src_slices]
362
- ][mask]
363
- dst_chunk.changed = True
364
- except IndexError as e:
365
- locals_copy = locals().copy()
366
- import traceback
367
-
368
- numpy_threshold = numpy.get_printoptions()["threshold"]
369
- numpy.set_printoptions(threshold=sys.maxsize)
370
- log_path = os.path.join(
371
- os.environ["LOG_DIR"], "clone_error.log"
372
- )
373
- os.makedirs(os.path.dirname(log_path), exist_ok=True)
374
- with open(log_path, "w") as f:
375
- for k, v in locals_copy.items():
376
- f.write(f"{k}: {v}\n\n")
377
- numpy.set_printoptions(threshold=numpy_threshold)
378
- raise IndexError(
379
- f"Error pasting.\nPlease notify the developers and include the file {log_path}.\n{e}"
380
- ) from e
381
-
382
- if include_entities:
383
- # TODO: implement pasting entities when we support entities
384
- pass
385
-
386
- count += 1
387
- yield count / iter_count
388
-
389
- yield 1.0
1
+ import sys
2
+ from typing import TYPE_CHECKING, Tuple, Generator, Optional
3
+ import numpy
4
+ import os
5
+
6
+ from amulet.api.data_types import Dimension, BlockCoordinates, FloatTriplet
7
+ from amulet.api.selection import SelectionGroup, SelectionBox
8
+ from amulet.api.block import Block, UniversalAirBlock
9
+ from amulet.api.errors import ChunkDoesNotExist, ChunkLoadError
10
+ from amulet.api.chunk import Chunk
11
+ from amulet.api.registry import BlockManager
12
+ from amulet.utils.matrix import transform_matrix, displacement_matrix
13
+ import amulet.api.level
14
+
15
+ if TYPE_CHECKING:
16
+ from .base_level import BaseLevel
17
+
18
+
19
+ def is_sub_block(skip_blocks: Tuple[Block, ...], b: Block) -> bool:
20
+ """Is the Block `b` a sub-block of any block in skip_blocks."""
21
+ for skip_block in skip_blocks:
22
+ if skip_block.namespaced_name == b.namespaced_name:
23
+ other_properties = b.properties
24
+ if skip_block.properties == {
25
+ key: other_properties[key] for key in skip_block.properties.keys()
26
+ }:
27
+ return True
28
+ return False
29
+
30
+
31
+ def gen_paste_blocks(
32
+ block_palette: BlockManager, skip_blocks: Tuple[Block, ...]
33
+ ) -> numpy.ndarray:
34
+ """Create a numpy bool array of all the blocks which should be pasted.
35
+
36
+ :param block_palette: The block palette of the chunk.
37
+ :param skip_blocks: Blocks to not copy. If a property is not defined it will match any value.
38
+ :return: Bool array of which blocks to copy.
39
+ """
40
+ return numpy.invert(
41
+ numpy.vectorize(lambda b: is_sub_block(skip_blocks, b))(block_palette.blocks)
42
+ )
43
+
44
+
45
+ def clone(
46
+ src_structure: "BaseLevel",
47
+ src_dimension: Dimension,
48
+ src_selection: SelectionGroup,
49
+ dst_structure: "BaseLevel",
50
+ dst_dimension: Dimension,
51
+ dst_selection_bounds: SelectionGroup,
52
+ location: BlockCoordinates,
53
+ scale: FloatTriplet = (1.0, 1.0, 1.0),
54
+ rotation: FloatTriplet = (0.0, 0.0, 0.0),
55
+ include_blocks: bool = True,
56
+ include_entities: bool = True,
57
+ skip_blocks: Tuple[Block, ...] = (),
58
+ copy_chunk_not_exist: bool = False,
59
+ ) -> Generator[float, None, None]:
60
+ """Clone the source object data into the destination object with an optional transform.
61
+ The src and dst can be the same object.
62
+ Note this command may change in the future. Refer to all keyword arguments via the keyword.
63
+ :param src_structure: The source structure to paste into the destination structure.
64
+ :param src_dimension: The dimension of the source structure to use.
65
+ :param src_selection: The area of the source structure to copy.
66
+ :param dst_structure: The destination structure to paste into.
67
+ :param dst_dimension: The dimension of the destination structure to use.
68
+ :param dst_selection_bounds: The area of the destination structure that can be modified.
69
+ :param location: The location where the centre of the `src_structure` will be in the `dst_structure`
70
+ :param scale: The scale in the x, y and z axis. These can be negative to mirror.
71
+ :param rotation: The rotation in degrees around each of the axis.
72
+ :param include_blocks: Include blocks from the `src_structure`.
73
+ :param include_entities: Include entities from the `src_structure`.
74
+ :param skip_blocks: If a block matches a block in this list it will not be copied.
75
+ :param copy_chunk_not_exist: If a chunk does not exist in the source should it be copied over as air. Always False where `src_structure` is a World.
76
+ :return: A generator of floats from 0 to 1 with the progress of the paste operation.
77
+ """
78
+ location = tuple(location)
79
+ src_selection = src_selection.merge_boxes()
80
+ if include_blocks or include_entities:
81
+ # we actually have to do something
82
+ if isinstance(src_structure, amulet.api.level.World):
83
+ copy_chunk_not_exist = False
84
+
85
+ # TODO: look into if this can be a float so it will always be the exact middle
86
+ rotation_point: numpy.ndarray = (
87
+ (src_selection.max_array + src_selection.min_array) // 2
88
+ ).astype(int)
89
+
90
+ if src_structure is dst_structure and src_dimension == dst_dimension:
91
+ # copying from an object to itself in the same dimension.
92
+ # if the selections do not overlap this can be achieved directly
93
+ # if they do overlap the selection will first need extracting
94
+ # TODO: implement the above
95
+ if (
96
+ tuple(rotation_point) == location
97
+ and scale == (1.0, 1.0, 1.0)
98
+ and rotation == (0.0, 0.0, 0.0)
99
+ ):
100
+ # The src_object was pasted into itself at the same location. Nothing will change so do nothing.
101
+ return
102
+ src_structure = src_structure.extract_structure(
103
+ src_selection, src_dimension
104
+ )
105
+ src_dimension = src_structure.dimensions[0]
106
+
107
+ src_structure: "BaseLevel"
108
+
109
+ # TODO: I don't know if this is feasible for large boxes: get the intersection of the source and destination selections and iterate over that to minimise work
110
+ if any(rotation) or any(s != 1 for s in scale):
111
+ # if the selection needs transforming
112
+ rotation_radians = tuple(numpy.radians(rotation))
113
+ transform = numpy.matmul(
114
+ transform_matrix(scale, rotation_radians, location),
115
+ displacement_matrix(*-rotation_point),
116
+ )
117
+
118
+ last_src: Optional[Tuple[int, int]] = None
119
+ src_chunk: Optional[
120
+ Chunk
121
+ ] = None # None here means the chunk does not exist or failed to load. Treat it as if it was air.
122
+ last_dst: Optional[Tuple[int, int]] = None
123
+ dst_chunk: Optional[
124
+ Chunk
125
+ ] = None # None here means the chunk failed to load. Do not modify it.
126
+
127
+ sum_progress = 0
128
+ volumes = tuple(
129
+ box.sub_chunk_count() for box in src_selection.selection_boxes
130
+ )
131
+ sum_volumes = sum(volumes)
132
+ volumes = tuple(vol / sum_volumes for vol in volumes)
133
+
134
+ if include_blocks:
135
+ blocks_to_skip = set(skip_blocks)
136
+ for box_index, box in enumerate(src_selection.selection_boxes):
137
+ for progress, src_coords, dst_coords in box.transformed_points(
138
+ transform
139
+ ):
140
+ if src_coords is not None:
141
+ dst_cx, dst_cy, dst_cz = dst_coords[0] >> 4
142
+ if (dst_cx, dst_cz) != last_dst:
143
+ last_dst = dst_cx, dst_cz
144
+ try:
145
+ dst_chunk = dst_structure.get_chunk(
146
+ dst_cx, dst_cz, dst_dimension
147
+ )
148
+ except ChunkDoesNotExist:
149
+ dst_chunk = dst_structure.create_chunk(
150
+ dst_cx, dst_cz, dst_dimension
151
+ )
152
+ except ChunkLoadError:
153
+ dst_chunk = None
154
+
155
+ src_coords = numpy.floor(src_coords).astype(int)
156
+ # due to how the coords are found dst_coords will all be in the same sub-chunk
157
+ src_chunk_coords = src_coords >> 4
158
+
159
+ # split the src coords into which sub-chunks they came from
160
+ unique_chunks, inverse, counts = numpy.unique(
161
+ src_chunk_coords,
162
+ return_inverse=True,
163
+ return_counts=True,
164
+ axis=0,
165
+ )
166
+ chunk_indexes = numpy.argsort(inverse)
167
+ src_block_locations = numpy.split(
168
+ src_coords[chunk_indexes], numpy.cumsum(counts)[:-1]
169
+ )
170
+ dst_block_locations = numpy.split(
171
+ dst_coords[chunk_indexes], numpy.cumsum(counts)[:-1]
172
+ )
173
+ for chunk_location, src_blocks, dst_blocks in zip(
174
+ unique_chunks, src_block_locations, dst_block_locations
175
+ ):
176
+ # for each src sub-chunk
177
+ src_cx, src_cy, src_cz = chunk_location
178
+ if (src_cx, src_cz) != last_src:
179
+ last_src = src_cx, src_cz
180
+ try:
181
+ src_chunk = src_structure.get_chunk(
182
+ src_cx, src_cz, src_dimension
183
+ )
184
+ except ChunkLoadError:
185
+ src_chunk = None
186
+
187
+ if dst_chunk is not None:
188
+ if (
189
+ src_chunk is not None
190
+ and src_cy in src_chunk.blocks
191
+ ):
192
+ # TODO implement support for individual block rotation
193
+ block_ids = src_chunk.blocks.get_sub_chunk(
194
+ src_cy
195
+ )[tuple(src_blocks.T % 16)]
196
+
197
+ for block_id in numpy.unique(block_ids):
198
+ block = src_chunk.block_palette[block_id]
199
+ if not is_sub_block(skip_blocks, block):
200
+ mask = block_ids == block_id
201
+ dst_blocks_ = dst_blocks[mask]
202
+
203
+ transformed_block = src_structure.translation_manager.transform_universal_block(
204
+ block, transform
205
+ )
206
+
207
+ dst_chunk.blocks.get_sub_chunk(dst_cy)[
208
+ tuple(dst_blocks_.T % 16)
209
+ ] = dst_chunk.block_palette.get_add_block(
210
+ transformed_block
211
+ )
212
+
213
+ src_blocks_ = src_blocks[mask]
214
+ for src_location, dst_location in zip(
215
+ src_blocks_, dst_blocks_
216
+ ):
217
+ src_location = tuple(
218
+ src_location.tolist()
219
+ )
220
+ dst_location = tuple(
221
+ dst_location.tolist()
222
+ )
223
+
224
+ if (
225
+ src_location
226
+ in src_chunk.block_entities
227
+ ):
228
+ dst_chunk.block_entities[
229
+ dst_location
230
+ ] = src_chunk.block_entities[
231
+ src_location
232
+ ].new_at_location(
233
+ *dst_location
234
+ )
235
+ elif (
236
+ dst_location
237
+ in dst_chunk.block_entities
238
+ ):
239
+ del dst_chunk.block_entities[
240
+ dst_location
241
+ ]
242
+
243
+ dst_chunk.changed = True
244
+ elif UniversalAirBlock not in blocks_to_skip:
245
+ dst_chunk.blocks.get_sub_chunk(dst_cy)[
246
+ tuple(dst_blocks.T % 16)
247
+ ] = dst_chunk.block_palette.get_add_block(
248
+ UniversalAirBlock
249
+ )
250
+ for location in dst_blocks:
251
+ location = tuple(location.tolist())
252
+ if location in dst_chunk.block_entities:
253
+ del dst_chunk.block_entities[location]
254
+ dst_chunk.changed = True
255
+ yield sum_progress + volumes[box_index] * progress
256
+ sum_progress += volumes[box_index]
257
+
258
+ else:
259
+ # the selection can be cloned as is
260
+ # the transform from the structure location to the world location
261
+ offset = numpy.asarray(location).astype(int) - rotation_point
262
+ moved_min_location = src_selection.min_array + offset
263
+
264
+ iter_count = len(
265
+ list(
266
+ src_structure.get_moved_coord_slice_box(
267
+ src_dimension,
268
+ moved_min_location,
269
+ src_selection,
270
+ dst_structure.sub_chunk_size,
271
+ yield_missing_chunks=copy_chunk_not_exist,
272
+ )
273
+ )
274
+ )
275
+
276
+ count = 0
277
+
278
+ for (
279
+ src_chunk,
280
+ src_slices,
281
+ src_box,
282
+ (dst_cx, dst_cz),
283
+ dst_slices,
284
+ dst_box,
285
+ ) in src_structure.get_moved_chunk_slice_box(
286
+ src_dimension,
287
+ moved_min_location,
288
+ src_selection,
289
+ dst_structure.sub_chunk_size,
290
+ create_missing_chunks=copy_chunk_not_exist,
291
+ ):
292
+ src_chunk: Chunk
293
+ src_slices: Tuple[slice, slice, slice]
294
+ src_box: SelectionBox
295
+ dst_cx: int
296
+ dst_cz: int
297
+ dst_slices: Tuple[slice, slice, slice]
298
+ dst_box: SelectionBox
299
+
300
+ # load the destination chunk
301
+ try:
302
+ dst_chunk = dst_structure.get_chunk(dst_cx, dst_cz, dst_dimension)
303
+ except ChunkDoesNotExist:
304
+ dst_chunk = dst_structure.create_chunk(
305
+ dst_cx, dst_cz, dst_dimension
306
+ )
307
+ except ChunkLoadError:
308
+ count += 1
309
+ continue
310
+
311
+ if include_blocks:
312
+ # a boolean array specifying if each index should be pasted.
313
+ paste_blocks = gen_paste_blocks(
314
+ src_chunk.block_palette, skip_blocks
315
+ )
316
+
317
+ # create a look up table converting the source block ids to the destination block ids
318
+ gab = numpy.vectorize(
319
+ dst_chunk.block_palette.get_add_block, otypes=[numpy.uint32]
320
+ )
321
+ lut = gab(src_chunk.block_palette.blocks)
322
+
323
+ # iterate through all block entities in the chunk and work out if the block is going to be overwritten
324
+ remove_block_entities = []
325
+ for block_entity_location in dst_chunk.block_entities.keys():
326
+ if block_entity_location in dst_box:
327
+ chunk_block_entity_location = (
328
+ numpy.array(block_entity_location) - offset
329
+ )
330
+ chunk_block_entity_location[[0, 2]] %= 16
331
+ if paste_blocks[
332
+ src_chunk.blocks[tuple(chunk_block_entity_location)]
333
+ ]:
334
+ remove_block_entities.append(block_entity_location)
335
+ for block_entity_location in remove_block_entities:
336
+ del dst_chunk.block_entities[block_entity_location]
337
+
338
+ # copy over the source block entities if the source block is supposed to be pasted
339
+ for (
340
+ block_entity_location,
341
+ block_entity,
342
+ ) in src_chunk.block_entities.items():
343
+ if block_entity_location in src_box:
344
+ chunk_block_entity_location = numpy.array(
345
+ block_entity_location
346
+ )
347
+ chunk_block_entity_location[[0, 2]] %= 16
348
+ if paste_blocks[
349
+ src_chunk.blocks[tuple(chunk_block_entity_location)]
350
+ ]:
351
+ dst_chunk.block_entities.insert(
352
+ block_entity.new_at_location(
353
+ *offset + block_entity_location
354
+ )
355
+ )
356
+
357
+ try:
358
+ block_mask = src_chunk.blocks[src_slices]
359
+ mask = paste_blocks[block_mask]
360
+ dst_chunk.blocks[dst_slices][mask] = lut[
361
+ src_chunk.blocks[src_slices]
362
+ ][mask]
363
+ dst_chunk.changed = True
364
+ except IndexError as e:
365
+ locals_copy = locals().copy()
366
+ import traceback
367
+
368
+ numpy_threshold = numpy.get_printoptions()["threshold"]
369
+ numpy.set_printoptions(threshold=sys.maxsize)
370
+ log_path = os.path.join(
371
+ os.environ["LOG_DIR"], "clone_error.log"
372
+ )
373
+ os.makedirs(os.path.dirname(log_path), exist_ok=True)
374
+ with open(log_path, "w") as f:
375
+ for k, v in locals_copy.items():
376
+ f.write(f"{k}: {v}\n\n")
377
+ numpy.set_printoptions(threshold=numpy_threshold)
378
+ raise IndexError(
379
+ f"Error pasting.\nPlease notify the developers and include the file {log_path}.\n{e}"
380
+ ) from e
381
+
382
+ if include_entities:
383
+ # TODO: implement pasting entities when we support entities
384
+ pass
385
+
386
+ count += 1
387
+ yield count / iter_count
388
+
389
+ yield 1.0