def test_intersects(self): box_1 = SelectionBox((0, 0, 0), (5, 5, 5)) box_2 = SelectionBox((4, 4, 4), (10, 10, 10)) box_3 = SelectionBox((5, 5, 5), (10, 10, 10)) box_4 = SelectionBox((1, 20, 1), (4, 25, 4)) self.assertTrue(box_1.intersects(box_2)) self.assertTrue(box_2.intersects(box_1)) self.assertFalse(box_1.intersects(box_3)) self.assertFalse(box_3.intersects(box_1)) self.assertFalse(box_1.intersects(box_4)) self.assertFalse(box_4.intersects(box_1))
def test_is_rectangular(self): box_1 = SelectionBox((0, 0, 0), (5, 5, 5)) box_2 = SelectionBox((0, 5, 0), (5, 10, 5)) box_3 = SelectionBox((0, 0, 5), (5, 5, 10)) box_4 = SelectionBox((0, 5, 5), (5, 10, 10)) self.assertTrue(SelectionGroup(box_1).is_rectangular) self.assertTrue(SelectionGroup((box_1, box_2)).is_rectangular) self.assertFalse(SelectionGroup((box_1, box_2, box_3)).is_rectangular) self.assertTrue(SelectionGroup((box_1, box_2, box_3, box_4)).is_rectangular) self.assertTrue( SelectionGroup((box_1, box_2, box_3, box_4, box_2)).is_rectangular )
def test_replace_single_block(self): subbox1 = SelectionBox((1, 70, 3), (5, 71, 6)) box1 = SelectionGroup((subbox1, )) self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, OVERWORLD).blockstate, ) self.assertEqual( "universal_minecraft:granite[polished=false]", self.world.get_block(1, 70, 5, OVERWORLD).blockstate, ) replace( self.world, OVERWORLD, box1, { "original_blocks": [ Block.from_string_blockstate( "universal_minecraft:granite[polished=false]") ], "replacement_blocks": [ Block.from_string_blockstate( "universal_minecraft:stone") ], }, ) self.world.create_undo_point() self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, OVERWORLD).blockstate, ) self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 5, OVERWORLD).blockstate, ) self.world.undo() self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, OVERWORLD).blockstate, ) self.assertEqual( "universal_minecraft:granite[polished=false]", self.world.get_block(1, 70, 5, OVERWORLD).blockstate, ) self.world.redo() self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, OVERWORLD).blockstate, ) self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 5, OVERWORLD).blockstate, )
def set_selection_corners( self, selection_corners: Tuple[Tuple[Tuple[int, int, int], Tuple[int, int, int]], ...], ): """Set the minimum and maximum points of each selection Note this method will not trigger the history logic. You may instead want the selection_corners setter method. :param selection_corners: The minimum and maximum points of each selection :return: """ selections = [] for points in selection_corners: if len(points) == 2 and all(len(point) == 3 for point in points): selections.append( tuple(tuple(int(p) for p in point) for point in points)) else: log.error( f"selection_corners must be of the format Tuple[Tuple[Tuple[int, int, int], Tuple[int, int, int]], ...]" ) self.changed = True self._selection_corners = tuple(selections) self._selection_group = SelectionGroup( [SelectionBox(*box) for box in self._selection_corners]) wx.PostEvent(self._canvas(), SelectionChangeEvent())
def test_replace_single_block(self): subbox1 = SelectionBox((1, 70, 3), (5, 71, 6)) box1 = SelectionGroup((subbox1,)) self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, "overworld").blockstate, ) self.assertEqual( 'universal_minecraft:granite[polished="false"]', self.world.get_block(1, 70, 5, "overworld").blockstate, ) self.world.run_operation( replace, "overworld", box1, { "original_blocks": [ blockstate_to_block( "universal_minecraft:granite[polished=false]" ) ], "replacement_blocks": [ blockstate_to_block("universal_minecraft:stone") ], }, ) self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, "overworld").blockstate, ) self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 5, "overworld").blockstate, ) self.world.undo() self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, "overworld").blockstate, ) self.assertEqual( 'universal_minecraft:granite[polished="false"]', self.world.get_block(1, 70, 5, "overworld").blockstate, ) self.world.redo() self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, "overworld").blockstate, ) self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 5, "overworld").blockstate, )
def test_single_block_box(self): box_1 = SelectionBox((0, 0, 0), (1, 1, 2)) self.assertEqual((1, 1, 2), box_1.shape) self.assertEqual(2, len([x for x in box_1])) self.assertIn((0, 0, 0), box_1) self.assertNotIn((1, 1, 2), box_1)
def load_level_from_world(level_folder, coord_box): # World is a amulet data structure world = load_level(level_folder) coord_start, coord_end = coord_box box = SelectionBox(coord_start, coord_end) # Level is a np array of Blocks coord_offset = np.array(box.min) print("Offset: {}".format(coord_offset)) level = level_from_world(world, box) signs = get_signs(world, box, coord_offset) return level, signs
def test_sorted_iterator(self): box_1 = SelectionBox((0, 0, 0), (4, 4, 4)) box_2 = SelectionBox((7, 7, 7), (10, 10, 10)) box_3 = SelectionBox((15, 15, 15), (20, 20, 20)) selection_1 = SelectionGroup( ( box_1, box_2, box_3, ) ) selection_2 = SelectionGroup( ( box_1, box_3, box_2, ) ) self.assertEqual(selection_1, selection_2)
def test_subtract(self): box_1 = SelectionGroup( SelectionBox( (0, 0, 0), (32, 32, 32), ) ) box_2 = SelectionGroup( SelectionBox( (0, 0, 0), (16, 16, 16), ) ) box_3 = box_1.subtract(box_2) box_4 = SelectionGroup( ( SelectionBox((0, 16, 0), (32, 32, 32)), SelectionBox((0, 0, 16), (32, 16, 32)), SelectionBox((16, 0, 0), (32, 16, 16)), ) ) self.assertEqual(box_3, box_4)
def __init__(self, path: PathOrBuffer, mode: str = "r"): super().__init__(path) assert mode in ("r", "w"), 'Mode must be either "r" or "w".' self._mode = mode if isinstance(path, str): assert path.endswith(".schematic"), 'Path must end with ".schematic"' if mode == "r": assert os.path.isfile(path), "File specified does not exist." self._data: Optional[Union[SchematicWriter, SchematicReader]] = None self._open = False self._platform = "java" self._version = (1, 12, 2) self._selection: SelectionBox = SelectionBox((0, 0, 0), (0, 0, 0))
def test_fill_operation(self): subbox_1 = SelectionBox((1, 70, 3), (5, 71, 5)) selection = SelectionGroup((subbox_1, )) # Start sanity check self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, OVERWORLD).blockstate, ) self.assertEqual( "universal_minecraft:granite[polished=false]", self.world.get_block(1, 70, 5, OVERWORLD).blockstate, ) # End sanity check generator_unpacker( fill( self.world, OVERWORLD, selection, Block.from_string_blockstate("universal_minecraft:stone"), )) self.world.create_undo_point() for x, y, z in selection.blocks: self.assertEqual( "universal_minecraft:stone", self.world.get_block(x, y, z, OVERWORLD).blockstate, f"Failed at coordinate ({x},{y},{z})", ) self.world.undo() self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, OVERWORLD).blockstate, ) self.assertEqual( "universal_minecraft:granite[polished=false]", self.world.get_block(1, 70, 5, OVERWORLD).blockstate, ) self.world.redo() for x, y, z in selection.blocks: self.assertEqual( "universal_minecraft:stone", self.world.get_block(x, y, z, OVERWORLD).blockstate, f"Failed at coordinate ({x},{y},{z})", )
def create(self, args: List[str]): if len(args) < 4: print('Usage: box.create "name" <x1,y1,z1> <x2,y2,z2> ....') box_name = args[1] if len(args[2:]) % 2 != 0: self.error("You must have an even amount of coordinate pairs") return if len(args[2:]) == 2: p1 = parse_coordinates(args[2]) p2 = parse_coordinates(args[3]) selection_box = SelectionBox((SubBox(p1, p2), )) else: selection_box = SelectionBox() for start in range(0, len(args[2:]), 2): p1 = parse_coordinates(args[2 + start]) p2 = parse_coordinates(args[3 + start]) selection_box.add_box(SubBox(p1, p2)) self.handler.shared_data["boxes"][box_name] = selection_box
def test_delete_chunk(self): subbox1 = SelectionBox((1, 1, 1), (5, 5, 5)) box1 = SelectionGroup((subbox1, )) self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, OVERWORLD).blockstate, ) self.assertEqual( "universal_minecraft:granite[polished=false]", self.world.get_block(1, 70, 5, OVERWORLD).blockstate, ) generator_unpacker(delete_chunk(self.world, OVERWORLD, box1)) self.world.create_undo_point() with self.assertRaises(ChunkDoesNotExist): _ = self.world.get_block(1, 70, 3, OVERWORLD).blockstate self.assertEqual( 0, len([ x for x in self.world.get_chunk_slice_box( OVERWORLD, subbox1) ]), ) self.world.undo() self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, OVERWORLD).blockstate, ) self.assertEqual( "universal_minecraft:granite[polished=false]", self.world.get_block(1, 70, 5, OVERWORLD).blockstate, ) self.world.redo() with self.assertRaises(ChunkDoesNotExist): _ = self.world.get_block(1, 70, 3, OVERWORLD).blockstate self.assertEqual( 0, len([ x for x in self.world.get_chunk_slice_box( OVERWORLD, subbox1) ]), )
def test_get_blocks(self): selection_box = SelectionBox((0, 0, 0), (10, 10, 10)) for selection in [SelectionGroup([selection_box]), selection_box]: chunk, box = next(self.world.get_chunk_boxes(selection, "overworld")) self.assertIsInstance(chunk, Chunk) self.assertIsInstance(box, SelectionBox) chunk, slices, _ = next( self.world.get_chunk_slices(selection, "overworld") ) self.assertIsInstance(chunk, Chunk) self.assertIsInstance(slices, tuple) for s in slices: self.assertIsInstance(s, slice)
def test_delete_chunk(self): subbox1 = SelectionBox((1, 1, 1), (5, 5, 5)) box1 = SelectionGroup((subbox1, )) self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, "overworld").blockstate, ) self.assertEqual( 'universal_minecraft:granite[polished="false"]', self.world.get_block(1, 70, 5, "overworld").blockstate, ) self.world.run_operation(delete_chunk, "overworld", box1) with self.assertRaises(ChunkDoesNotExist): _ = self.world.get_block(1, 70, 3, "overworld").blockstate self.assertEqual( 0, len([ x for x in self.world.get_chunk_slice_box( "overworld", subbox1) ]), ) self.world.undo() self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, "overworld").blockstate, ) self.assertEqual( 'universal_minecraft:granite[polished="false"]', self.world.get_block(1, 70, 5, "overworld").blockstate, ) self.world.redo() with self.assertRaises(ChunkDoesNotExist): _ = self.world.get_block(1, 70, 3, "overworld").blockstate self.assertEqual( 0, len([ x for x in self.world.get_chunk_slice_box( "overworld", subbox1) ]), )
def _on_input_release(self, evt: InputReleaseEvent): if evt.action_id == ACT_BOX_CLICK: if self._editing and time.time() - self._press_time > 0.1: self._editing = False box = SelectionBox(*self._get_editing_selection()) if SelectionGroup(box).is_subset( self._selection.selection_group): # subtract self._selection.selection_group = ( self._selection.selection_group.subtract(box)) else: self._selection.selection_group = ( self._selection.selection_group.union(box)) self._create_undo() evt.Skip()
def test_fill_operation(self): subbox_1 = SelectionBox((1, 70, 3), (5, 71, 5)) box = SelectionGroup((subbox_1,)) # Start sanity check self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, "overworld").blockstate, ) self.assertEqual( 'universal_minecraft:granite[polished="false"]', self.world.get_block(1, 70, 5, "overworld").blockstate, ) # End sanity check self.world.run_operation( fill, "overworld", box, blockstate_to_block("universal_minecraft:stone") ) for x, y, z in box: self.assertEqual( "universal_minecraft:stone", self.world.get_block(x, y, z, "overworld").blockstate, f"Failed at coordinate ({x},{y},{z})", ) self.world.undo() self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, "overworld").blockstate, ) self.assertEqual( 'universal_minecraft:granite[polished="false"]', self.world.get_block(1, 70, 5, "overworld").blockstate, ) self.world.redo() for x, y, z in box: self.assertEqual( "universal_minecraft:stone", self.world.get_block(x, y, z, "overworld").blockstate, f"Failed at coordinate ({x},{y},{z})", )
def test_clone_operation(self): subbx1 = SelectionBox((1, 70, 3), (2, 71, 4)) src_box = SelectionGroup((subbx1, )) target = {"x": 1, "y": 70, "z": 5} self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, OVERWORLD).blockstate, ) # Sanity check self.assertEqual( "universal_minecraft:granite[polished=false]", self.world.get_block(1, 70, 5, OVERWORLD).blockstate, ) clone(self.world, OVERWORLD, src_box, target) self.world.create_undo_point() self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 5, OVERWORLD).blockstate, ) self.world.undo() self.assertEqual( "universal_minecraft:granite[polished=false]", self.world.get_block(1, 70, 5, OVERWORLD).blockstate, ) self.world.redo() self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 5, OVERWORLD).blockstate, ) self.world.undo() self.assertEqual( "universal_minecraft:granite[polished=false]", self.world.get_block(1, 70, 5, OVERWORLD).blockstate, )
def test_clone_operation(self): subbx1 = SelectionBox((1, 70, 3), (2, 71, 4)) src_box = SelectionGroup((subbx1,)) target = {"x": 1, "y": 70, "z": 5} self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, "overworld").blockstate, ) # Sanity check self.assertEqual( 'universal_minecraft:granite[polished="false"]', self.world.get_block(1, 70, 5, "overworld").blockstate, ) self.world.run_operation(clone, "overworld", src_box, target) self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 5, "overworld").blockstate, ) self.world.undo() self.assertEqual( 'universal_minecraft:granite[polished="false"]', self.world.get_block(1, 70, 5, "overworld").blockstate, ) self.world.redo() self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 5, "overworld").blockstate, ) self.world.undo() self.assertEqual( 'universal_minecraft:granite[polished="false"]', self.world.get_block(1, 70, 5, "overworld").blockstate, )
def _chunkify_selection(self): selections = [] for box in self.canvas.selection.selection_group.selection_boxes: min_point = ( numpy.floor(box.min_array / self.canvas.world.sub_chunk_size) * self.canvas.world.sub_chunk_size) max_point = ( numpy.ceil(box.max_array / self.canvas.world.sub_chunk_size) * self.canvas.world.sub_chunk_size) bounds = self.canvas.world.bounds(self.canvas.dimension) min_point[1] = bounds.min[1] max_point[1] = bounds.max[1] selections.append(SelectionBox(min_point, max_point)) selection_group = SelectionGroup(selections) if selection_group != self.canvas.selection.selection_group: # if the above code modified the selection self.canvas.selection.selection_group = selection_group # this will indirectly update the renderer by updating the global selection elif selection_group != self._selection.selection_group: # if the above code did not change the selection but it does not match the renderer self._selection.selection_group = selection_group
def test_get_entities( self, ): # TODO: Make a more complete test once we figure out what get_entities() returns box1 = SelectionGroup((SelectionBox((0, 0, 0), (17, 20, 17)), )) test_entity = { "id": "universal_minecraft:cow", "CustomName": "TestName", "Pos": [1.0, 4.0, 1.0], } entity_iter = self.world.get_entities_in_box(box1) for chunk_coords, entities in entity_iter: self.assertEqual(0, len(entities)) self.world.add_entities([test_entity]) entity_iter = self.world.get_entities_in_box(box1) for chunk_coords, entities in entity_iter: if chunk_coords == (0, 0): self.assertEqual(1, len(entities)) test_entity["Pos"] = entities[0]["Pos"] = [ 17.0, 20.0, 17.0 ] else: self.assertEqual(0, len(entities)) entity_iter = self.world.get_entities_in_box(box1) for chunk_coords, entities in entity_iter: if chunk_coords == (1, 1): self.assertEqual(1, len(entities)) else: self.assertEqual(0, len(entities)) self.world.delete_entities([test_entity]) entity_iter = self.world.get_entities_in_box(box1) for chunk_coords, entities in entity_iter: self.assertEqual(0, len(entities))
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 _shallow_load(self): if os.path.isfile(self.path): with open(self.path, "rb") as f: magic_num_1 = f.read(magic_num_len) if magic_num_1 == magic_num: format_version = struct.unpack(">B", f.read(1))[0] if format_version == 0: f.seek(-magic_num_len, os.SEEK_END) magic_num_2 = f.read(magic_num_len) if magic_num_2 == magic_num: f.seek(-magic_num_len - INT_STRUCT.size, os.SEEK_END) metadata_end = f.tell() metadata_start = INT_STRUCT.unpack( f.read(INT_STRUCT.size))[0] f.seek(metadata_start) metadata = amulet_nbt.load( f.read(metadata_end - metadata_start), compressed=True, ) self._platform = metadata["export_version"][ "edition"].value self._version = tuple( map( lambda v: v.value, metadata["export_version"]["version"], )) selection_boxes = ( metadata["selection_boxes"].value.reshape( -1, 6).tolist()) self._bounds[self.dimensions[0]] = SelectionGroup([ SelectionBox((minx, miny, minz), (maxx, maxy, maxz)) for minx, miny, minz, maxx, maxy, maxz in selection_boxes ])
def selection_box(self) -> SelectionBox: return SelectionBox(self.point1, self.point2)
def _test_create( self, cls: Type[FormatWrapper], level_name: str, platform: str, version: VersionNumberAny, ): path = clean_temp_world(level_name) # create, initialise and save the level level = cls(path) if level.requires_selection: level.create_and_open( platform, version, SelectionGroup([SelectionBox((0, 0, 0), (1, 1, 1))]), overwrite=True, ) else: level.create_and_open(platform, version, overwrite=True) self.assertTrue(level.is_open, "The level was not opened by create_and_open()") self.assertTrue(level.has_lock, "The lock was not acquired by create_and_open()") platform_ = level.platform version_ = level.version dimension_selections = { dim: level.bounds(dim) for dim in level.dimensions } level.save() level.close() self.assertFalse(level.is_open, "The level was not closed by close()") self.assertFalse(level.has_lock, "The lock was not lost by close()") self.assertTrue(os.path.exists(level.path)) # reopen the level level2 = load_format(path) # check that the class is the same self.assertIs(level.__class__, level2.__class__) level2.open() self.assertTrue(level2.is_open, "The level was not opened by open()") self.assertTrue(level2.has_lock, "The lock was not acquired by open()") # check that the platform and version are the same self.assertEqual(level2.platform, platform_) self.assertEqual(level2.version, version_) self.assertEqual(set(level2.dimensions), set(dimension_selections)) for dim, selection in dimension_selections.items(): self.assertEqual(level2.bounds(dim), selection) level2.close() self.assertFalse(level2.is_open, "The level was not closed by close()") self.assertFalse(level2.has_lock, "The lock was not lost by close()") self.assertTrue(os.path.exists(level.path)) level = cls(path) with self.assertRaises(ObjectWriteError): if level.requires_selection: level.create_and_open( platform, version, SelectionGroup([SelectionBox((0, 0, 0), (1, 1, 1))]), ) else: level.create_and_open(platform, version) clean_path(path)
def create_selection_group(self) -> SelectionGroup: return SelectionGroup([ SelectionBox(box.min, box.max) for box in self._boxes if box.is_static ])
def get_moved_coord_slice_box( self, dimension: Dimension, destination_origin: BlockCoordinates, selection: Optional[Union[SelectionGroup, SelectionBox]] = None, destination_sub_chunk_shape: Optional[int] = None, yield_missing_chunks: bool = False, ) -> Generator[Tuple[ChunkCoordinates, Tuple[ slice, slice, slice], SelectionBox, ChunkCoordinates, Tuple[ slice, slice, slice], SelectionBox, ], None, None, ]: """ Iterate over a selection and return slices into the source object and destination object given the origin of the destination. When copying a selection to a new area the slices will only be equal if the offset is a multiple of the chunk size. This will rarely be the case so the slices need to be split up into parts that intersect a chunk in the source and destination. :param dimension: The dimension to iterate over. :param destination_origin: The location where the minimum point of the selection will end up :param selection: An optional selection. The overlap of this and the dimensions bounds will be used :param destination_sub_chunk_shape: the chunk shape of the destination object (defaults to self.sub_chunk_size) :param yield_missing_chunks: Generate empty chunks if the chunk does not exist. :return: """ if destination_sub_chunk_shape is None: destination_sub_chunk_shape = self.sub_chunk_size if selection is None: selection = self.bounds(dimension) else: selection = self.bounds(dimension).intersection(selection) # the offset from self.selection to the destination location offset = numpy.subtract(destination_origin, self.bounds(dimension).min, dtype=int) for (src_cx, src_cz), box in self.get_coord_box( dimension, selection, yield_missing_chunks=yield_missing_chunks): dst_full_box = SelectionBox(offset + box.min, offset + box.max) first_chunk = block_coords_to_chunk_coords( dst_full_box.min_x, dst_full_box.min_z, sub_chunk_size=destination_sub_chunk_shape, ) last_chunk = block_coords_to_chunk_coords( dst_full_box.max_x - 1, dst_full_box.max_z - 1, sub_chunk_size=destination_sub_chunk_shape, ) for dst_cx, dst_cz in itertools.product( range(first_chunk[0], last_chunk[0] + 1), range(first_chunk[1], last_chunk[1] + 1), ): chunk_box = self._chunk_box(dst_cx, dst_cz, destination_sub_chunk_shape) dst_box = chunk_box.intersection(dst_full_box) src_box = SelectionBox(-offset + dst_box.min, -offset + dst_box.max) src_slices = src_box.chunk_slice(src_cx, src_cz, self.sub_chunk_size) dst_slices = dst_box.chunk_slice(dst_cx, dst_cz, self.sub_chunk_size) yield (src_cx, src_cz), src_slices, src_box, ( dst_cx, dst_cz, ), dst_slices, dst_box
def open_from(self, f: BinaryIO): f = BytesIO(f.read()) magic_num_1 = f.read(8) assert magic_num_1 == magic_num, f"This file is not a construction file." self._format_version = struct.unpack(">B", f.read(1))[0] if self._format_version == 0: f.seek(-magic_num_len, os.SEEK_END) magic_num_2 = f.read(8) assert ( magic_num_2 == magic_num ), "It looks like this file is corrupt. It probably wasn't saved properly" f.seek(-magic_num_len - INT_STRUCT.size, os.SEEK_END) metadata_end = f.tell() metadata_start = INT_STRUCT.unpack(f.read(INT_STRUCT.size))[0] f.seek(metadata_start) metadata = amulet_nbt.load( f.read(metadata_end - metadata_start), compressed=True, ) try: self._platform = metadata["export_version"]["edition"].value self._version = tuple( map(lambda v: v.value, metadata["export_version"]["version"]) ) except KeyError as e: raise KeyError(f'Missing export version identifying key "{e.args[0]}"') self._section_version = metadata["section_version"].value palette = unpack_palette(metadata["block_palette"]) selection_boxes = metadata["selection_boxes"].value.reshape(-1, 6).tolist() self._selection = SelectionGroup( [ SelectionBox((minx, miny, minz), (maxx, maxy, maxz)) for minx, miny, minz, maxx, maxy, maxz in selection_boxes ] ) self._populate_chunk_to_box() section_index_table = ( metadata["section_index_table"].value.view(SECTION_ENTRY_TYPE).tolist() ) if self._section_version == 0: for ( start_x, start_y, start_z, shape_x, shape_y, shape_z, position, length, ) in section_index_table: f.seek(position) nbt_obj = amulet_nbt.load(f.read(length)) if nbt_obj["blocks_array_type"].value == -1: blocks = None block_entities = None else: blocks = numpy.reshape( nbt_obj["blocks"].value, (shape_x, shape_y, shape_z) ) block_entities = parse_block_entities(nbt_obj["block_entities"]) start = numpy.array([start_x, start_y, start_z]) chunk_index: numpy.ndarray = start // self.sub_chunk_size shape = numpy.array([shape_x, shape_y, shape_z]) if numpy.any(shape <= 0): continue # skip sections with zero size if numpy.any( start + shape > (chunk_index + 1) * self.sub_chunk_size ): log.error( f"section in construction file did not fit in one sub-chunk. Start: {start}, Shape: {shape}" ) cx, cy, cz = chunk_index.tolist() self._chunk_to_section.setdefault((cx, cz), []).append( ConstructionSection( (start_x, start_y, start_z), (shape_x, shape_y, shape_z), blocks, palette, parse_entities(nbt_obj["entities"]), block_entities, ) ) else: raise Exception( f"This wrapper does not support any construction section version higher than {max_section_version}" ) else: raise Exception( f"This wrapper does not support any construction format version higher than {max_format_version}" )
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 test_replace_multiblock(self): subbox1 = SelectionBox((1, 70, 3), (2, 75, 4)) box1 = SelectionGroup((subbox1,)) self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, "overworld").blockstate, ) for y in range(71, 75): self.assertEqual( "universal_minecraft:air", self.world.get_block(1, y, 3, "overworld").blockstate, f"Failed at coordinate (1,{y},3)", ) self.world.run_operation( replace, "overworld", box1, { "original_blocks": [ blockstate_to_block("universal_minecraft:stone"), blockstate_to_block("universal_minecraft:air"), ], "replacement_blocks": [ blockstate_to_block( "universal_minecraft:granite[polished=false]" ), blockstate_to_block("universal_minecraft:stone"), ], }, ) self.assertEqual( 'universal_minecraft:granite[polished="false"]', self.world.get_block(1, 70, 3, "overworld").blockstate, ) for y in range(71, 75): self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, y, 3, "overworld").blockstate, f"Failed at coordinate (1,{y},3)", ) self.world.undo() self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, "overworld").blockstate, ) for y in range(71, 75): self.assertEqual( "universal_minecraft:air", self.world.get_block(1, y, 3, "overworld").blockstate, f"Failed at coordinate (1,{y},3)", ) self.world.redo() self.assertEqual( 'universal_minecraft:granite[polished="false"]', self.world.get_block(1, 70, 3, "overworld").blockstate, ) for y in range(71, 75): self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, y, 3, "overworld").blockstate, f"Failed at coordinate (1,{y},3)", )