Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 73 additions & 14 deletions src/mcstructure/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,11 @@ def get_block(self, coordinate: Coordinate) -> Block | None:
The coordinte of the block.
"""
x, y, z = coordinate
return self._palette[self.structure[x, y, z]]
return (
self._palette[self.structure[x, y, z]]
if self.structure[x, y, z] >= 0
else Block("minecraft:structure_void")
)

def set_block(
self,
Expand Down Expand Up @@ -579,7 +583,9 @@ def set_blocks(
).reshape([abs(i) + 1 for i in (fx - tx, fy - ty, fz - tz)])
return self

def resize(self, size: tuple[int, int, int], fill: Block | None = Block("minecraft:air")) -> Self:
def resize(
self, size: tuple[int, int, int], fill: Block | None = Block("minecraft:air")
) -> Self:
"""
Resizes the structure.

Expand Down Expand Up @@ -607,22 +613,75 @@ def resize(self, size: tuple[int, int, int], fill: Block | None = Block("minecra
new_structure = np.full(size, ident, dtype=np.intc)
else:
new_structure = np.full(size, -1, dtype=np.intc) # Fill Structure void

# Calculate the overlap region (what we can copy from old to new)
old_size = self.structure.shape
copy_size = tuple(min(old_size[i], size[i]) for i in range(3))

# Copy the existing Blocks that fits into the new size
if all(s > 0 for s in copy_size):
new_structure[
:copy_size[0],
:copy_size[1],
:copy_size[2]
] = self.structure[
:copy_size[0],
:copy_size[1],
:copy_size[2]
]

new_structure[: copy_size[0], : copy_size[1], : copy_size[2]] = (
self.structure[: copy_size[0], : copy_size[1], : copy_size[2]]
)

self.structure = new_structure
return self

def combine(self, other: Structure, position: Coordinate = (0, 0, 0)) -> Structure:
"""
Combines another structure into this structure.

Parameters
----------
other
The other structure to combine with.

position
The position to place the other structure at.

note: This position is relative to the
current structure's origin. ( no negative coordinates allowed )

Returns
-------
Structure
The combined structure.
"""

ox, oy, oz = position

if ox < 0 or oy < 0 or oz < 0:
raise ValueError("Negative coordinates are not allowed.")

# Calculate the new size needed to accommodate both structures
end_pos = (ox + other._size[0], oy + other._size[1], oz + other._size[2])
new_size = tuple(max(self._size[i], end_pos[i]) for i in range(3))

combined = Structure(new_size, None)

# Copy the current structure's palette
combined._palette = self._palette.copy()

# Copy the current structure
combined.structure[: self._size[0], : self._size[1], : self._size[2]] = (
self.structure
)

# Create mapping from other's palette indices to combined palette indices
other_to_combined_mapping = {
other_idx: combined._add_block_to_palette(block)
for other_idx, block in enumerate(other._palette)
}

# Remap the entire other structure using vectorized operations
remapped_structure = other.structure.copy()

# For non-structure-void blocks, apply the palette mapping
for other_idx, combined_idx in other_to_combined_mapping.items():
block_mask = other.structure == other_idx
remapped_structure[block_mask] = combined_idx

# Overlay the remapped structure onto the combined structure
combined.structure[ox:, oy:, oz:] = remapped_structure[:, :, :]

return combined
22 changes: 22 additions & 0 deletions tests/test_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ def test_oversized() -> None:
assert has_suitable_size(STRUCTURE_MAX_SIZE)
assert not has_suitable_size((65, 0, 0))


def test_resize_larger() -> None:
dirt = Block("minecraft:dirt")
air = Block("minecraft:air")
Expand All @@ -16,6 +17,7 @@ def test_resize_larger() -> None:
assert struct.get_block((1, 1, 1)) == dirt
assert struct.get_block((2, 2, 2)) == air


def test_resize_smaller() -> None:
dirt = Block("minecraft:dirt")
struct = Structure((4, 4, 4), fill=dirt)
Expand All @@ -26,3 +28,23 @@ def test_resize_smaller() -> None:
assert struct.get_block((2, 2, 2)) is None
assert struct.get_block((1, 1, 1)) == dirt
assert struct.get_block((0, 0, 0)) == dirt


def test_combine() -> None:
dirt = Block("minecraft:dirt")
air = Block("minecraft:air")
void = Block("minecraft:structure_void")
struct_A = Structure((1, 2, 2), fill=air)
struct_B = Structure((1, 2, 2), fill=dirt)
struct_C = struct_A.combine(struct_B, (0, 1, 1))

# Check the combined structure
assert struct_C.get_block((0, 0, 0)) == air
assert struct_C.get_block((0, 0, 1)) == air
assert struct_C.get_block((0, 0, 2)) == void
assert struct_C.get_block((0, 1, 0)) == air
assert struct_C.get_block((0, 1, 1)) == dirt
assert struct_C.get_block((0, 1, 2)) == dirt
assert struct_C.get_block((0, 2, 0)) == void
assert struct_C.get_block((0, 2, 1)) == dirt
assert struct_C.get_block((0, 2, 2)) == dirt