Skip to content

Commit cdf1103

Browse files
Add an option to control which chunks will be modified (#453)
1 parent 91ee085 commit cdf1103

File tree

3 files changed

+47
-7
lines changed

3 files changed

+47
-7
lines changed

amulet/api/level/base_level/base_level.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import annotations
22

3-
import time
43
from typing import Union, Generator, Optional, Tuple, Callable, Set, Iterable
54
import traceback
65
import numpy
@@ -32,7 +31,7 @@
3231
from amulet.utils.world_utils import block_coords_to_chunk_coords
3332
from .chunk_manager import ChunkManager
3433
from amulet.api.history.history_manager import MetaHistoryManager
35-
from .clone import clone
34+
from .clone import clone, PasteRule
3635
from amulet.api import wrapper as api_wrapper, level as api_level
3736
import PyMCTranslate
3837
from amulet.api.player import Player
@@ -657,6 +656,7 @@ def paste(
657656
include_entities: bool = True,
658657
skip_blocks: Tuple[Block, ...] = (),
659658
copy_chunk_not_exist: bool = False,
659+
paste_rule: PasteRule = PasteRule.PasteAll,
660660
):
661661
"""Paste a level into this level at the given location.
662662
Note this command may change in the future.
@@ -672,6 +672,7 @@ def paste(
672672
:param include_entities: Include entities when pasting the structure.
673673
:param skip_blocks: If a block matches a block in this list it will not be copied.
674674
:param copy_chunk_not_exist: If a chunk does not exist in the source should it be copied over as air. Always False where level is a World.
675+
:param paste_rule: Control which chunks can be pasted into.
675676
:return:
676677
"""
677678
return generator_unpacker(
@@ -687,6 +688,7 @@ def paste(
687688
include_entities,
688689
skip_blocks,
689690
copy_chunk_not_exist,
691+
paste_rule,
690692
)
691693
)
692694

@@ -703,6 +705,7 @@ def paste_iter(
703705
include_entities: bool = True,
704706
skip_blocks: Tuple[Block, ...] = (),
705707
copy_chunk_not_exist: bool = False,
708+
paste_rule: PasteRule = PasteRule.PasteAll,
706709
) -> Generator[float, None, None]:
707710
"""Paste a structure into this structure at the given location.
708711
Note this command may change in the future.
@@ -718,6 +721,7 @@ def paste_iter(
718721
:param include_entities: Include entities when pasting the structure.
719722
:param skip_blocks: If a block matches a block in this list it will not be copied.
720723
:param copy_chunk_not_exist: If a chunk does not exist in the source should it be copied over as air. Always False where level is a World.
724+
:param paste_rule: Control which chunks can be pasted into.
721725
:return: A generator of floats from 0 to 1 with the progress of the paste operation.
722726
"""
723727
yield from clone(
@@ -734,6 +738,7 @@ def paste_iter(
734738
include_entities,
735739
skip_blocks,
736740
copy_chunk_not_exist,
741+
paste_rule,
737742
)
738743

739744
def get_version_block(

amulet/api/level/base_level/clone.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import TYPE_CHECKING, Tuple, Generator, Optional
33
import numpy
44
import os
5+
from enum import Enum
56

67
from amulet.api.data_types import Dimension, BlockCoordinates, FloatTriplet
78
from amulet.api.selection import SelectionGroup, SelectionBox
@@ -42,6 +43,12 @@ def gen_paste_blocks(
4243
)
4344

4445

46+
class PasteRule(Enum):
47+
PasteAll = "PasteAll" # Paste in all chunk.
48+
PasteExist = "PasteExist" # Paste only in chunks that exist.
49+
PasteNotExist = "PasteNotExist" # Paste only in chunks that do not exist.
50+
51+
4552
def clone(
4653
src_structure: "BaseLevel",
4754
src_dimension: Dimension,
@@ -56,6 +63,7 @@ def clone(
5663
include_entities: bool = True,
5764
skip_blocks: Tuple[Block, ...] = (),
5865
copy_chunk_not_exist: bool = False,
66+
paste_rule: PasteRule = PasteRule.PasteAll,
5967
) -> Generator[float, None, None]:
6068
"""Clone the source object data into the destination object with an optional transform.
6169
The src and dst can be the same object.
@@ -73,6 +81,7 @@ def clone(
7381
:param include_entities: Include entities from the `src_structure`.
7482
:param skip_blocks: If a block matches a block in this list it will not be copied.
7583
: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.
84+
:param paste_rule: Control which chunks can be pasted into.
7685
:return: A generator of floats from 0 to 1 with the progress of the paste operation.
7786
"""
7887
location = tuple(location)
@@ -106,6 +115,10 @@ def clone(
106115

107116
src_structure: "BaseLevel"
108117

118+
# If the paste mode is PasteNotExist we need to create the chunk and track that we created it
119+
# so that future modifications in this operation are allowed.
120+
created_chunks = set[tuple[int, int]]()
121+
109122
# 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
110123
if any(rotation) or any(s != 1 for s in scale):
111124
# if the selection needs transforming
@@ -137,6 +150,7 @@ def clone(
137150
for progress, src_coords, dst_coords in box.transformed_points(
138151
transform
139152
):
153+
yield sum_progress + volumes[box_index] * progress
140154
if src_coords is not None:
141155
dst_cx, dst_cy, dst_cz = dst_coords[0] >> 4
142156
if (dst_cx, dst_cz) != last_dst:
@@ -146,11 +160,20 @@ def clone(
146160
dst_cx, dst_cz, dst_dimension
147161
)
148162
except ChunkDoesNotExist:
163+
if paste_rule == PasteRule.PasteExist:
164+
continue
149165
dst_chunk = dst_structure.create_chunk(
150166
dst_cx, dst_cz, dst_dimension
151167
)
168+
created_chunks.add((dst_cx, dst_cz))
152169
except ChunkLoadError:
153170
dst_chunk = None
171+
else:
172+
if (
173+
paste_rule == PasteRule.PasteNotExist
174+
and (dst_cx, dst_cz) not in created_chunks
175+
):
176+
continue
154177

155178
src_coords = numpy.floor(src_coords).astype(int)
156179
# due to how the coords are found dst_coords will all be in the same sub-chunk
@@ -252,7 +275,6 @@ def clone(
252275
if location in dst_chunk.block_entities:
253276
del dst_chunk.block_entities[location]
254277
dst_chunk.changed = True
255-
yield sum_progress + volumes[box_index] * progress
256278
sum_progress += volumes[box_index]
257279

258280
else:
@@ -297,16 +319,27 @@ def clone(
297319
dst_slices: Tuple[slice, slice, slice]
298320
dst_box: SelectionBox
299321

322+
yield count / iter_count
323+
count += 1
324+
300325
# load the destination chunk
301326
try:
302327
dst_chunk = dst_structure.get_chunk(dst_cx, dst_cz, dst_dimension)
303328
except ChunkDoesNotExist:
329+
if paste_rule == PasteRule.PasteExist:
330+
continue
304331
dst_chunk = dst_structure.create_chunk(
305332
dst_cx, dst_cz, dst_dimension
306333
)
334+
created_chunks.add((dst_cx, dst_cz))
307335
except ChunkLoadError:
308-
count += 1
309336
continue
337+
else:
338+
if (
339+
paste_rule == PasteRule.PasteNotExist
340+
and (dst_cx, dst_cz) not in created_chunks
341+
):
342+
continue
310343

311344
if include_blocks:
312345
# a boolean array specifying if each index should be pasted.
@@ -383,7 +416,4 @@ def clone(
383416
# TODO: implement pasting entities when we support entities
384417
pass
385418

386-
count += 1
387-
yield count / iter_count
388-
389419
yield 1.0

amulet/operations/paste.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from amulet.api.data_types import Dimension, BlockCoordinates, FloatTriplet
66
from amulet.api.block import Block, UniversalAirLikeBlocks
7+
from amulet.api.level.base_level.clone import PasteRule
78

89
if TYPE_CHECKING:
910
from amulet.api.level import BaseLevel
@@ -20,6 +21,7 @@ def paste(
2021
copy_air=True,
2122
copy_water=True,
2223
copy_lava=True,
24+
paste_rule: PasteRule = PasteRule.PasteAll,
2325
):
2426
for _ in paste_iter(
2527
dst,
@@ -32,6 +34,7 @@ def paste(
3234
copy_air,
3335
copy_water,
3436
copy_lava,
37+
paste_rule,
3538
):
3639
pass
3740

@@ -47,6 +50,7 @@ def paste_iter(
4750
copy_air=True,
4851
copy_water=True,
4952
copy_lava=True,
53+
paste_rule: PasteRule = PasteRule.PasteAll,
5054
):
5155
yield from dst.paste_iter(
5256
src,
@@ -62,4 +66,5 @@ def paste_iter(
6266
+ (Block("universal_minecraft", "water"),) * bool(not copy_water)
6367
+ (Block("universal_minecraft", "lava"),) * bool(not copy_lava),
6468
True,
69+
paste_rule,
6570
)

0 commit comments

Comments
 (0)