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.
- amulet/__init__.py +27 -27
- amulet/__pyinstaller/__init__.py +2 -2
- amulet/__pyinstaller/hook-amulet.py +4 -4
- amulet/_version.py +21 -21
- amulet/api/__init__.py +2 -2
- amulet/api/abstract_base_entity.py +128 -128
- amulet/api/block.py +630 -630
- amulet/api/block_entity.py +71 -71
- amulet/api/cache.py +107 -107
- amulet/api/chunk/__init__.py +6 -6
- amulet/api/chunk/biomes.py +207 -207
- amulet/api/chunk/block_entity_dict.py +175 -175
- amulet/api/chunk/blocks.py +46 -46
- amulet/api/chunk/chunk.py +389 -389
- amulet/api/chunk/entity_list.py +75 -75
- amulet/api/chunk/status.py +167 -167
- amulet/api/data_types/__init__.py +4 -4
- amulet/api/data_types/generic_types.py +4 -4
- amulet/api/data_types/operation_types.py +16 -16
- amulet/api/data_types/world_types.py +49 -49
- amulet/api/data_types/wrapper_types.py +71 -71
- amulet/api/entity.py +74 -74
- amulet/api/errors.py +119 -119
- amulet/api/history/__init__.py +36 -36
- amulet/api/history/base/__init__.py +3 -3
- amulet/api/history/base/base_history.py +26 -26
- amulet/api/history/base/history_manager.py +63 -63
- amulet/api/history/base/revision_manager.py +73 -73
- amulet/api/history/changeable.py +15 -15
- amulet/api/history/data_types.py +7 -7
- amulet/api/history/history_manager/__init__.py +3 -3
- amulet/api/history/history_manager/container.py +102 -102
- amulet/api/history/history_manager/database.py +279 -279
- amulet/api/history/history_manager/meta.py +93 -93
- amulet/api/history/history_manager/object.py +116 -116
- amulet/api/history/revision_manager/__init__.py +2 -2
- amulet/api/history/revision_manager/disk.py +33 -33
- amulet/api/history/revision_manager/ram.py +12 -12
- amulet/api/item.py +75 -75
- amulet/api/level/__init__.py +4 -4
- amulet/api/level/base_level/__init__.py +1 -1
- amulet/api/level/base_level/base_level.py +1035 -1026
- amulet/api/level/base_level/chunk_manager.py +227 -227
- amulet/api/level/base_level/clone.py +389 -389
- amulet/api/level/base_level/player_manager.py +101 -101
- amulet/api/level/immutable_structure/__init__.py +1 -1
- amulet/api/level/immutable_structure/immutable_structure.py +94 -94
- amulet/api/level/immutable_structure/void_format_wrapper.py +117 -117
- amulet/api/level/structure.py +22 -22
- amulet/api/level/world.py +19 -19
- amulet/api/partial_3d_array/__init__.py +2 -2
- amulet/api/partial_3d_array/base_partial_3d_array.py +263 -263
- amulet/api/partial_3d_array/bounded_partial_3d_array.py +528 -528
- amulet/api/partial_3d_array/data_types.py +15 -15
- amulet/api/partial_3d_array/unbounded_partial_3d_array.py +229 -229
- amulet/api/partial_3d_array/util.py +152 -152
- amulet/api/player.py +65 -65
- amulet/api/registry/__init__.py +2 -2
- amulet/api/registry/base_registry.py +34 -34
- amulet/api/registry/biome_manager.py +153 -153
- amulet/api/registry/block_manager.py +156 -156
- amulet/api/selection/__init__.py +2 -2
- amulet/api/selection/abstract_selection.py +315 -315
- amulet/api/selection/box.py +805 -805
- amulet/api/selection/group.py +488 -488
- amulet/api/structure.py +37 -37
- amulet/api/wrapper/__init__.py +8 -8
- amulet/api/wrapper/chunk/interface.py +441 -441
- amulet/api/wrapper/chunk/translator.py +567 -567
- amulet/api/wrapper/format_wrapper.py +772 -772
- amulet/api/wrapper/structure_format_wrapper.py +116 -116
- amulet/api/wrapper/world_format_wrapper.py +63 -63
- amulet/level/__init__.py +1 -1
- amulet/level/formats/anvil_forge_world.py +40 -40
- amulet/level/formats/anvil_world/__init__.py +3 -3
- amulet/level/formats/anvil_world/_sector_manager.py +291 -384
- amulet/level/formats/anvil_world/data_pack/__init__.py +2 -2
- amulet/level/formats/anvil_world/data_pack/data_pack.py +224 -224
- amulet/level/formats/anvil_world/data_pack/data_pack_manager.py +77 -77
- amulet/level/formats/anvil_world/dimension.py +177 -177
- amulet/level/formats/anvil_world/format.py +769 -769
- amulet/level/formats/anvil_world/region.py +384 -384
- amulet/level/formats/construction/__init__.py +3 -3
- amulet/level/formats/construction/format_wrapper.py +515 -515
- amulet/level/formats/construction/interface.py +134 -134
- amulet/level/formats/construction/section.py +60 -60
- amulet/level/formats/construction/util.py +165 -165
- amulet/level/formats/leveldb_world/__init__.py +3 -3
- amulet/level/formats/leveldb_world/chunk.py +33 -33
- amulet/level/formats/leveldb_world/dimension.py +385 -419
- amulet/level/formats/leveldb_world/format.py +659 -641
- amulet/level/formats/leveldb_world/interface/chunk/__init__.py +36 -36
- amulet/level/formats/leveldb_world/interface/chunk/base_leveldb_interface.py +836 -836
- amulet/level/formats/leveldb_world/interface/chunk/generate_interface.py +31 -31
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_0.py +30 -30
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_1.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_10.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_11.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_12.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_13.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_14.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_15.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_16.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_17.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_18.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_19.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_2.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_20.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_21.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_22.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_23.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_24.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_25.py +24 -24
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_26.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_27.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_28.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_29.py +33 -33
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_3.py +57 -57
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_30.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_31.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_32.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_33.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_34.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_35.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_36.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_37.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_38.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_39.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_4.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_40.py +16 -16
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_5.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_6.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_7.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_8.py +180 -180
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_9.py +18 -18
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_chunk_versions.py +79 -79
- amulet/level/formats/mcstructure/__init__.py +3 -3
- amulet/level/formats/mcstructure/chunk.py +50 -50
- amulet/level/formats/mcstructure/format_wrapper.py +408 -408
- amulet/level/formats/mcstructure/interface.py +175 -175
- amulet/level/formats/schematic/__init__.py +3 -3
- amulet/level/formats/schematic/chunk.py +55 -55
- amulet/level/formats/schematic/data_types.py +4 -4
- amulet/level/formats/schematic/format_wrapper.py +373 -373
- amulet/level/formats/schematic/interface.py +142 -142
- amulet/level/formats/sponge_schem/__init__.py +4 -4
- amulet/level/formats/sponge_schem/chunk.py +62 -62
- amulet/level/formats/sponge_schem/format_wrapper.py +463 -463
- amulet/level/formats/sponge_schem/interface.py +118 -118
- amulet/level/formats/sponge_schem/varint/__init__.py +1 -1
- amulet/level/formats/sponge_schem/varint/varint.py +87 -87
- amulet/level/interfaces/chunk/anvil/anvil_0.py +72 -72
- amulet/level/interfaces/chunk/anvil/anvil_1444.py +336 -336
- amulet/level/interfaces/chunk/anvil/anvil_1466.py +94 -94
- amulet/level/interfaces/chunk/anvil/anvil_1467.py +37 -37
- amulet/level/interfaces/chunk/anvil/anvil_1484.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1503.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1519.py +34 -34
- amulet/level/interfaces/chunk/anvil/anvil_1901.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1908.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1912.py +21 -21
- amulet/level/interfaces/chunk/anvil/anvil_1934.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_2203.py +69 -69
- amulet/level/interfaces/chunk/anvil/anvil_2529.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_2681.py +76 -76
- amulet/level/interfaces/chunk/anvil/anvil_2709.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_2844.py +267 -267
- amulet/level/interfaces/chunk/anvil/anvil_3463.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_na.py +607 -607
- amulet/level/interfaces/chunk/anvil/base_anvil_interface.py +326 -326
- amulet/level/load.py +59 -59
- amulet/level/loader.py +95 -95
- amulet/level/translators/chunk/bedrock/__init__.py +267 -267
- amulet/level/translators/chunk/bedrock/bedrock_nbt_blockstate_translator.py +46 -46
- amulet/level/translators/chunk/bedrock/bedrock_numerical_translator.py +39 -39
- amulet/level/translators/chunk/bedrock/bedrock_psudo_numerical_translator.py +37 -37
- amulet/level/translators/chunk/java/java_1_18_translator.py +40 -40
- amulet/level/translators/chunk/java/java_blockstate_translator.py +94 -94
- amulet/level/translators/chunk/java/java_numerical_translator.py +62 -62
- amulet/libs/leveldb/__init__.py +7 -7
- amulet/operations/__init__.py +5 -5
- amulet/operations/clone.py +18 -18
- amulet/operations/delete_chunk.py +32 -32
- amulet/operations/fill.py +30 -30
- amulet/operations/paste.py +65 -65
- amulet/operations/replace.py +58 -58
- amulet/utils/__init__.py +14 -14
- amulet/utils/format_utils.py +41 -41
- amulet/utils/generator.py +15 -15
- amulet/utils/matrix.py +243 -243
- amulet/utils/numpy_helpers.py +46 -46
- amulet/utils/world_utils.py +349 -349
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/METADATA +97 -97
- amulet_core-1.9.20.dist-info/RECORD +208 -0
- amulet_core-1.9.19.dist-info/RECORD +0 -208
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/WHEEL +0 -0
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/entry_points.txt +0 -0
- {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
|