class SchematicReader: def __init__(self, path_or_buffer: PathOrBuffer): if isinstance(path_or_buffer, str): assert path_or_buffer.endswith( ".schematic"), "File selected is not a .schematic file" assert os.path.isfile( path_or_buffer ), f"There is no schematic file at path {path_or_buffer}" schematic = amulet_nbt.load(path_or_buffer) assert not all(key in schematic for key in ( "Version", "Data Version", "BlockData")), "This file is not a legacy schematic file." else: assert hasattr(path_or_buffer, "read"), "Object does not have a read method" schematic = amulet_nbt.load(buffer=path_or_buffer) materials = schematic.get("Materials", amulet_nbt.TAG_String()).value if materials == "Alpha": self._platform = "java" elif materials == "Pocket": self._platform = "bedrock" else: raise Exception( f'"{materials}" is not a supported platform for a schematic file.' ) self._chunks: Dict[ChunkCoordinates, Tuple[SelectionBox, BlockArrayType, BlockDataArrayType, list, list], ] = {} self._selection = SelectionBox( (0, 0, 0), ( schematic["Width"].value, schematic["Height"].value, schematic["Length"].value, ), ) entities: amulet_nbt.TAG_List = schematic.get("Entities", amulet_nbt.TAG_List()) block_entities: amulet_nbt.TAG_List = schematic.get( "TileEntities", amulet_nbt.TAG_List()) blocks: numpy.ndarray = schematic["Blocks"].value.astype( numpy.uint8).astype(numpy.uint16) if "AddBlocks" in schematic: add_blocks = schematic["AddBlocks"] blocks = blocks + (numpy.concatenate([ [(add_blocks & 0xF0) >> 4], [add_blocks & 0xF] ]).T.ravel().astype(numpy.uint16) << 8)[:blocks.size] max_point = self._selection.max temp_shape = (max_point[1], max_point[2], max_point[0]) blocks = numpy.transpose(blocks.reshape(temp_shape), (2, 0, 1)) # YZX => XYZ data = numpy.transpose(schematic["Data"].value.reshape(temp_shape), (2, 0, 1)) for cx, cz in self._selection.chunk_locations(): box = SelectionBox( (cx * 16, 0, cz * 16), ( min((cx + 1) * 16, self._selection.size_x), self._selection.size_y, min((cz + 1) * 16, self._selection.size_z), ), ) self._chunks[(cx, cz)] = (box, blocks[box.slice], data[box.slice], [], []) for e in block_entities: if all(key in e for key in ("x", "y", "z")): x, y, z = e["x"].value, e["y"].value, e["z"].value if (x, y, z) in self._selection: cx = x >> 4 cz = z >> 4 self._chunks[(cx, cz)][3].append(e) for e in entities: if "Pos" in e: pos: PointCoordinates = tuple( map(lambda t: t.value, e["Pos"].value)) if pos in self._selection: cx = int(pos[0]) >> 4 cz = int(pos[2]) >> 4 self._chunks[(cx, cz)][4].append(e) def read(self, cx: int, cz: int): if (cx, cz) in self._chunks: return SchematicChunk(*self._chunks[(cx, cz)]) else: raise ChunkDoesNotExist @property def platform(self) -> str: return self._platform @property def selection(self) -> SelectionBox: return self._selection @property def chunk_coords(self) -> Generator[ChunkCoordinates, None, None]: yield from self._chunks.keys() def close(self): pass
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 open_from(self, f: BinaryIO): schematic = amulet_nbt.load(f) if any(key in schematic for key in ("Version", "Data Version", "BlockData")): raise ObjectReadError("This file is not a legacy schematic file.") materials = schematic.get("Materials", amulet_nbt.TAG_String()).value if materials == "Alpha": self._platform = "java" self._version = (1, 12, 2) elif materials == "Pocket": self._platform = "bedrock" self._version = (1, 12, 0) else: raise Exception( f'"{materials}" is not a supported platform for a schematic file.' ) self._chunks = {} selection_box = SelectionBox( (0, 0, 0), ( schematic["Width"].value, schematic["Height"].value, schematic["Length"].value, ), ) self._selection = SelectionGroup(selection_box) entities: amulet_nbt.TAG_List = schematic.get("Entities", amulet_nbt.TAG_List()) block_entities: amulet_nbt.TAG_List = schematic.get( "TileEntities", amulet_nbt.TAG_List()) blocks: numpy.ndarray = (schematic["Blocks"].value.astype( numpy.uint8).astype(numpy.uint16)) if "AddBlocks" in schematic: add_blocks = schematic["AddBlocks"] blocks = (blocks + (numpy.concatenate([ [(add_blocks & 0xF0) >> 4], [add_blocks & 0xF] ]).T.ravel().astype(numpy.uint16) << 8)[:blocks.size]) max_point = selection_box.max temp_shape = (max_point[1], max_point[2], max_point[0]) blocks = numpy.transpose(blocks.reshape(temp_shape), (2, 0, 1)) # YZX => XYZ data = numpy.transpose(schematic["Data"].value.reshape(temp_shape), (2, 0, 1)).astype(numpy.uint8) for cx, cz in selection_box.chunk_locations(): box = SelectionBox( (cx * self.sub_chunk_size, 0, cz * self.sub_chunk_size), ( min((cx + 1) * self.sub_chunk_size, selection_box.size_x), selection_box.size_y, min((cz + 1) * self.sub_chunk_size, selection_box.size_z), ), ) self._chunks[(cx, cz)] = (box, blocks[box.slice], data[box.slice], [], []) for e in block_entities: if all(key in e for key in ("x", "y", "z")): x, y, z = e["x"].value, e["y"].value, e["z"].value if (x, y, z) in selection_box: cx = x >> 4 cz = z >> 4 self._chunks[(cx, cz)][3].append(e) for e in entities: if "Pos" in e: pos: PointCoordinates = tuple( map(lambda t: float(t.value), e["Pos"].value)) if pos in selection_box: cx = int(pos[0]) >> 4 cz = int(pos[2]) >> 4 self._chunks[(cx, cz)][4].append(e)
class MCStructureReader: 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 read(self, cx: int, cz: int): if (cx, cz) in self._chunks: return MCStructureChunk(*self._chunks[(cx, cz)]) else: raise ChunkDoesNotExist @property def selection(self) -> SelectionBox: return self._selection @property def chunk_coords(self) -> Generator[ChunkCoordinates, None, None]: yield from self._chunks.keys() def close(self): pass
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." )