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 _on_input_press(self, evt: InputPressEvent): if evt.action_id == ACT_BOX_CLICK: if not self._editing: self._press_time = time.time() self._editing = True self._start_box = self._pointer.bounds self._update_pointer() elif evt.action_id == ACT_DESELECT_ALL_BOXES: self._selection.selection_group = SelectionGroup() self._create_undo() elif evt.action_id == ACT_DELESECT_BOX: if ACT_DESELECT_ALL_BOXES not in self.canvas.buttons.pressed_actions: self._selection.selection_group = SelectionGroup() self._create_undo() evt.Skip()
def get_chunk_boxes( self, selection: Union[SelectionGroup, SelectionBox], dimension: Dimension, create_missing_chunks=False, ) -> Generator[Tuple[Chunk, SelectionBox], None, None]: """Given a selection will yield chunks and SubSelectionBoxes into that chunk :param selection: SelectionGroup or SelectionBox into the world :param dimension: The dimension to take effect in (defaults to overworld) :param create_missing_chunks: If a chunk does not exist an empty one will be created (defaults to false) """ if isinstance(selection, SelectionBox): selection = SelectionGroup([selection]) selection: SelectionGroup for (cx, cz), box in selection.sub_sections(self.sub_chunk_size): try: chunk = self.get_chunk(cx, cz, dimension) except ChunkDoesNotExist: if create_missing_chunks: chunk = Chunk(cx, cz) self.put_chunk(chunk, dimension) else: continue except ChunkLoadError: continue yield chunk, box
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, ) 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 __init__(self, canvas: "EditCanvas"): super().__init__() self._selection_corners: Tuple[BoxType, ...] = () self._selection_group: SelectionGroup = SelectionGroup() self._canvas = weakref.ref(canvas) self._timer = wx.Timer(canvas)
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 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 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 _sanitise_selection(self, selection: Union[SelectionGroup, SelectionBox, None], dimension: Dimension) -> SelectionGroup: if isinstance(selection, SelectionBox): return SelectionGroup(selection) elif isinstance(selection, SelectionGroup): return selection elif selection is None: return self.bounds(dimension) else: raise ValueError( f"Expected SelectionBox, SelectionGroup or None. Got {selection}" )
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 _clean_selection(self, selection: SelectionGroup) -> SelectionGroup: if self.multi_selection: return selection else: if selection: return SelectionGroup( sorted( selection.selection_boxes, reverse=True, key=lambda b: b.volume, )[0]) else: raise ObjectReadError( "A single selection was required but none were given.")
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 __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(".construction"), 'Path must end with ".construction"' if mode == "r": assert os.path.isfile(path), "File specified does not exist." self._data: Optional[Union[ConstructionWriter, ConstructionReader]] = None self._open = False self._platform = "java" self._version = (1, 15, 2) self._selection: SelectionGroup = SelectionGroup() # used to look up which sections are in a given chunk when loading self._chunk_to_section: Optional[Dict[Tuple[int, int], List[int]]] = None # used to look up which selection boxes intersect a given chunk (boxes are clipped to the size of the chunk) self._chunk_to_box: Optional[Dict[Tuple[int, int], List[SelectionBox]]] = None
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 selection_group(self, selection_group: SelectionGroup): """Set the selection group of the static and active selection. This will only trigger a grapical change and will not update the global selection. A call to push_selection is required to push the updated selection to the global selection.""" self._escape() if len(selection_group) == 0: # unload the active selection if self._active_selection is not None: self._unload_active_selection() if len(selection_group) <= 1: # unload the selection group self._selection.selection_group = SelectionGroup() if len(selection_group) >= 1: # load the active selection self._create_active_selection() self._active_selection.selection_box = selection_group[-1] if len(selection_group) >= 2: # load the selection group self._selection.selection_group = selection_group[:-1]
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 _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 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)", )
def selection_group(self) -> SelectionGroup: return SelectionGroup(self.selection_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}" )