def _chunk_box(self, cx: int, cz: int, sub_chunk_size: Optional[int] = None): """Get a SelectionBox containing the whole of a given chunk""" if sub_chunk_size is None: sub_chunk_size = self.sub_chunk_size return SelectionBox.create_chunk_box(cx, cz, sub_chunk_size)
def _encode( self, chunk: Chunk, chunk_palette: AnyNDArray, interface: SchematicInterface, ): return interface.encode( chunk, chunk_palette, self.max_world_version, SelectionBox.create_chunk_box(chunk.cx, chunk.cz).intersection( self._selection ), )
def _encode( self, interface: MCStructureInterface, chunk: Chunk, dimension: Dimension, chunk_palette: AnyNDArray, ): return interface.encode( chunk, chunk_palette, self.max_world_version, SelectionBox.create_chunk_box(chunk.cx, chunk.cz).intersection( self._bounds[dimension].to_box()), )
def get_coord_box( self, dimension: Dimension, selection: Union[SelectionGroup, SelectionBox, None] = None, yield_missing_chunks=False, ) -> Generator[Tuple[ChunkCoordinates, SelectionBox], None, None]: """Given a selection will yield chunk coordinates and `SelectionBox`es into that chunk If not given a selection will use the bounds of the object. :param selection: SelectionGroup or SelectionBox into the level :param dimension: The dimension to take effect in :param yield_missing_chunks: If a chunk does not exist an empty one will be created (defaults to false). Use this with care. """ if isinstance(selection, SelectionBox): selection = SelectionGroup(selection) elif selection is None: selection = self.selection_bounds elif not isinstance(selection, SelectionGroup): raise TypeError( f"Expected a SelectionGroup but got {type(selection)}") selection: SelectionGroup if yield_missing_chunks or selection.footprint_area < 1_000_000: if yield_missing_chunks: for coord, box in selection.chunk_boxes(self.sub_chunk_size): yield coord, box else: for (cx, cz), box in selection.chunk_boxes(self.sub_chunk_size): if self.has_chunk(cx, cz, dimension): yield (cx, cz), box else: # if the selection gets very large iterating over the whole selection and accessing chunks can get slow # instead we are going to iterate over the chunks and get the intersection of the selection for cx, cz in self.all_chunk_coords(dimension): box = SelectionGroup( SelectionBox.create_chunk_box(cx, cz, self.sub_chunk_size)) if selection.intersects(box): chunk_selection = selection.intersection(box) for sub_box in chunk_selection.selection_boxes: yield (cx, cz), sub_box
def open(self): """Open the database for reading and writing""" if self._open: return if self._mode == "r": assert ( isinstance(self.path_or_buffer, str) and os.path.isfile(self.path_or_buffer) ) or hasattr(self.path_or_buffer, "read"), "File specified does not exist." self._data = ConstructionReader(self.path_or_buffer) self._platform = self._data.source_edition self._version = self._data.source_version self._selection = SelectionGroup( [ SelectionBox((minx, miny, minz), (maxx, maxy, maxz)) for minx, miny, minz, maxx, maxy, maxz in self._data.selection ] ) self._chunk_to_section = {} for index, (x, _, z, _, _, _, _, _) in enumerate(self._data.sections): cx = x >> 4 cz = z >> 4 self._chunk_to_section.setdefault((cx, cz), []).append(index) else: self._data = ConstructionWriter( self.path_or_buffer, self.platform, self.version, [box.bounds for box in self.selection.selection_boxes], ) self._chunk_to_box = {} for box in self.selection.selection_boxes: for cx, cz in box.chunk_locations(): self._chunk_to_box.setdefault((cx, cz), []).append( box.intersection(SelectionBox.create_chunk_box(cx, cz)) ) self._open = True
def _populate_chunk_to_box(self): for box in self._selection.selection_boxes: for cx, cz in box.chunk_locations(): self._chunk_to_box.setdefault((cx, cz), []).append( SelectionBox.create_chunk_box(cx, cz).intersection(box) )
def open_from(self, f: BinaryIO): mcstructure = amulet_nbt.load(f, little_endian=True) if mcstructure["format_version"].value == 1: min_point = numpy.array( tuple(c.value for c in mcstructure["structure_world_origin"]) ) max_point = min_point + tuple(c.value for c in mcstructure["size"]) selection = SelectionBox(min_point, max_point) self._selection = SelectionGroup(selection) translator_version = self.translation_manager.get_version( "bedrock", (999, 999, 999) ) self._platform = translator_version.platform self._version = translator_version.version_number blocks_array: numpy.ndarray = numpy.array( [ [b.value for b in layer] for layer in mcstructure["structure"]["block_indices"] ], dtype=numpy.int32, ).reshape( (len(mcstructure["structure"]["block_indices"]), *selection.shape) ) palette_key = list(mcstructure["structure"]["palette"].keys())[ 0 ] # find a way to do this based on user input block_palette = list( mcstructure["structure"]["palette"][palette_key]["block_palette"] ) for cx, cz in selection.chunk_locations(): chunk_box = SelectionBox.create_chunk_box(cx, cz).intersection( selection ) array_slice = (slice(None),) + chunk_box.create_moved_box( selection.min, subtract=True ).slice chunk_blocks_: numpy.ndarray = blocks_array[array_slice] chunk_palette_indexes, chunk_blocks = numpy.unique( chunk_blocks_.reshape((chunk_blocks_.shape[0], -1)).T, return_inverse=True, axis=0, ) chunk_blocks = chunk_blocks.reshape(chunk_blocks_.shape[1:]) chunk_palette = numpy.empty(len(chunk_palette_indexes), dtype=object) for palette_index, indexes in enumerate(chunk_palette_indexes): chunk_palette[palette_index] = tuple( block_palette[index] for index in indexes if index >= 0 ) self._chunks[(cx, cz)] = ( chunk_box, chunk_blocks, chunk_palette, [], [], ) block_entities = { int(key): val["block_entity_data"] for key, val in mcstructure["structure"]["palette"][palette_key][ "block_position_data" ].items() if "block_entity_data" in val } for location, block_entity in block_entities.items(): if all(key in block_entity for key in "xyz"): x, y, z = ( block_entity["x"].value, block_entity["y"].value, block_entity["z"].value, ) cx, cz = x >> 4, z >> 4 if (cx, cz) in self._chunks and (x, y, z) in self._chunks[(cx, cz)][ 0 ]: self._chunks[(cx, cz)][3].append(block_entity) entities = list(mcstructure["structure"]["entities"]) for entity in entities: if "Pos" in entity: x, y, z = ( entity["Pos"][0].value, entity["Pos"][1].value, entity["Pos"][2].value, ) cx, cz = numpy.floor([x, z]).astype(numpy.int) >> 4 if (cx, cz) in self._chunks and (x, y, z) in self._chunks[(cx, cz)][ 0 ]: self._chunks[(cx, cz)][4].append(entity) else: raise Exception( f"mcstructure file with format_version=={mcstructure['format_version'].value} cannot be read" )
def __init__(self, path_or_buffer: Union[str, IO]): if isinstance(path_or_buffer, str): assert path_or_buffer.endswith( ".mcstructure"), "File selected is not a .mcstructure file" assert os.path.isfile( path_or_buffer ), f"There is no mcstructure file at path {path_or_buffer}" mcstructure = amulet_nbt.load(path_or_buffer, little_endian=True) else: assert hasattr(path_or_buffer, "read"), "Object does not have a read method" mcstructure = amulet_nbt.load(buffer=path_or_buffer, little_endian=True) self._chunks: Dict[ChunkCoordinates, Tuple[SelectionBox, numpy.ndarray, AnyNDArray, List[amulet_nbt.TAG_Compound], List[amulet_nbt.TAG_Compound], ], ] = {} if mcstructure["format_version"].value == 1: min_point = numpy.array( tuple(c.value for c in mcstructure["structure_world_origin"])) max_point = min_point + tuple(c.value for c in mcstructure["size"]) self._selection = SelectionBox(min_point, max_point) blocks_array: numpy.ndarray = numpy.array( [[b.value for b in layer] for layer in mcstructure["structure"]["block_indices"]], dtype=numpy.int32, ).reshape((len(mcstructure["structure"]["block_indices"]), *self._selection.shape)) palette_key = list( mcstructure["structure"]["block_palette"].keys())[ 0] # find a way to do this based on user input block_palette = list(mcstructure["structure"]["block_palette"] [palette_key]["block_palette"]) for cx, cz in self._selection.chunk_locations(): chunk_box = SelectionBox.create_chunk_box(cx, cz).intersection( self._selection) array_slice = (slice(None), ) + chunk_box.create_moved_box( self._selection.min, subtract=True).slice chunk_blocks_: numpy.ndarray = blocks_array[array_slice] chunk_palette_indexes, chunk_blocks = numpy.unique( chunk_blocks_.reshape((chunk_blocks_.shape[0], -1)).T, return_inverse=True, axis=0, ) chunk_blocks = chunk_blocks.reshape(chunk_blocks_.shape[1:]) chunk_palette = numpy.empty(len(chunk_palette_indexes), dtype=object) for palette_index, indexes in enumerate(chunk_palette_indexes): chunk_palette[palette_index] = tuple(block_palette[index] for index in indexes if index >= 0) self._chunks[(cx, cz)] = ( chunk_box, chunk_blocks, chunk_palette, [], [], ) block_entities = { int(key): val["block_entity_data"] for key, val in mcstructure["structure"]["block_palette"] [palette_key]["block_position_data"].items() if "block_entity_data" in val } for location, block_entity in block_entities.items(): if all(key in block_entity for key in "xyz"): x, y, z = ( block_entity["x"].value, block_entity["y"].value, block_entity["z"].value, ) cx, cz = x >> 4, z >> 4 if (cx, cz) in self._chunks and (x, y, z) in self._chunks[( cx, cz)][0]: self._chunks[(cx, cz)][3].append(block_entity) entities = list(mcstructure["structure"]["entities"]) for entity in entities: if "Pos" in entity: x, y, z = ( entity["Pos"][0].value, entity["Pos"][1].value, entity["Pos"][2].value, ) cx, cz = numpy.floor([x, z]).astype(numpy.int) >> 4 if (cx, cz) in self._chunks and (x, y, z) in self._chunks[( cx, cz)][0]: self._chunks[(cx, cz)][4].append(entity) else: raise Exception( f"mcstructure file with format_version=={mcstructure['format_version'].value} cannot be read" )
def open_from(self, f: BinaryIO): sponge_schem = amulet_nbt.load(f) version = sponge_schem.get("Version") if not isinstance(version, amulet_nbt.TAG_Int): raise SpongeSchemReadError( "Version key must exist and be an integer.") if version == 1: raise SpongeSchemReadError( "Sponge Schematic Version 1 is not supported currently.") elif version == 2: offset = sponge_schem.get("Offset") if isinstance(offset, amulet_nbt.TAG_Int_Array) and len(offset) == 3: min_point = numpy.array(offset) else: min_point = numpy.array([0, 0, 0], dtype=numpy.int32) size = [] for key in ("Width", "Height", "Length"): val = sponge_schem.get(key) if not isinstance(val, amulet_nbt.TAG_Short): raise SpongeSchemReadError( f"Key {key} must exist and be a TAG_Short.") # convert to an unsigned short val = val.value if val < 0: val += 2**16 size.append(val) max_point = min_point + size selection = SelectionBox(min_point, max_point) self._bounds[self.dimensions[0]] = SelectionGroup(selection) data_version = sponge_schem.get("DataVersion") if not isinstance(data_version, amulet_nbt.TAG_Int): raise SpongeSchemReadError("DataVersion must be a TAG_Int.") translator_version = self.translation_manager.get_version( "java", int(data_version)) self._platform = translator_version.platform self._version = translator_version.data_version packed_block_data = sponge_schem.get("BlockData") if not isinstance(packed_block_data, amulet_nbt.TAG_Byte_Array): raise SpongeSchemReadError( "BlockData must be a TAG_Byte_Array") unpacked_block_data = decode_byte_array( numpy.array(packed_block_data, dtype=numpy.uint8)) if len(unpacked_block_data) != numpy.prod(size): raise SpongeSchemReadError( "The data contained in BlockData does not match the size of the schematic." ) dx, dy, dz = selection.shape blocks_array: numpy.ndarray = numpy.transpose( numpy.array( unpacked_block_data, dtype=numpy.uint32, ).reshape((dy, dz, dx)), (2, 0, 1), # YZX => XYZ ) if "Palette" not in sponge_schem: raise SpongeSchemReadError( "Amulet is not able to read Sponge Schem files with no block palette." ) palette_data = sponge_schem.get("Palette") if not isinstance(palette_data, amulet_nbt.TAG_Compound): raise SpongeSchemReadError("Palette must be a TAG_Compound.") block_palette: Dict[int, Block] = {} for blockstate, index in palette_data.items(): if index.value in block_palette: raise SpongeSchemReadError( f"Duplicate block index {index} found in the palette.") block_palette[index.value] = Block.from_string_blockstate( blockstate) if not numpy.all(numpy.isin(blocks_array, list(block_palette))): raise SpongeSchemReadError( "Some values in BlockData were not present in Palette") for cx, cz in selection.chunk_locations(): chunk_box = SelectionBox.create_chunk_box( cx, cz).intersection(selection) array_slice = chunk_box.create_moved_box(selection.min, subtract=True).slice chunk_blocks_: numpy.ndarray = blocks_array[array_slice] chunk_palette_indexes, chunk_blocks = numpy.unique( chunk_blocks_, return_inverse=True, ) chunk_blocks = chunk_blocks.reshape(chunk_blocks_.shape) chunk_palette = numpy.empty(len(chunk_palette_indexes), dtype=object) for palette_index, index in enumerate(chunk_palette_indexes): chunk_palette[palette_index] = block_palette[index] self._chunks[(cx, cz)] = ( chunk_box, chunk_blocks, chunk_palette, [], [], ) if "BlockEntities" in sponge_schem: block_entities = sponge_schem["BlockEntities"] if (not isinstance(block_entities, amulet_nbt.TAG_List) or block_entities.list_data_type != 10 # amulet_nbt.TAG_Compound.tag_id ): raise SpongeSchemReadError( "BlockEntities must be a TAG_List of compound tags.") for block_entity in block_entities: if "Pos" in block_entity: pos = block_entity["Pos"] if isinstance( pos, amulet_nbt.TAG_Int_Array) and len(pos) == 3: pos = pos + min_point x, y, z = ( pos[0], pos[1], pos[2], ) block_entity["Pos"] = amulet_nbt.TAG_Int_Array(pos) cx, cz = x >> 4, z >> 4 if (cx, cz) in self._chunks and ( x, y, z) in self._chunks[(cx, cz)][0]: self._chunks[(cx, cz)][3].append(block_entity) if "Entities" in sponge_schem: entities = sponge_schem["Entities"] if (not isinstance(entities, amulet_nbt.TAG_List) or entities.list_data_type != 10 # amulet_nbt.TAG_Compound.tag_id ): raise SpongeSchemReadError( "Entities must be a TAG_List of compound tags.") for entity in entities: if "Pos" in entity: pos = entity["Pos"] if (isinstance(pos, amulet_nbt.TAG_List) and len(pos) == 3 and pos.list_data_type == 6): # amulet_nbt.TAG_Double.tag_id: x, y, z = ( pos[0].value + offset[0], pos[1].value + offset[0], pos[2].value + offset[0], ) entity["Pos"] = amulet_nbt.TAG_List([ amulet_nbt.TAG_Int(x), amulet_nbt.TAG_Int(y), amulet_nbt.TAG_Int(z), ]) cx, cz = numpy.floor([x, z]).astype(int) >> 4 if (cx, cz) in self._chunks and ( x, y, z) in self._chunks[(cx, cz)][0]: self._chunks[(cx, cz)][4].append(entity) else: raise SpongeSchemReadError( f"Sponge Schematic Version {version.value} is not supported currently." )