def _operation(self, world: "World", dimension: Dimension, selection: SelectionGroup) -> OperationReturnType: if len(selection.selection_boxes) == 0: raise OperationError("No selection was given to export.") elif len(selection.selection_boxes) != 1: raise OperationError( "The mcstructure format only supports a single selection box.") path = self._file_picker.GetPath() version = self._version_define.version_number if isinstance(path, str) and path.endswith(".mcstructure") and version: wrapper = MCStructureFormatWrapper(path, "w") wrapper.selection = selection wrapper.version = version wrapper.translation_manager = world.translation_manager wrapper.open() chunk_count = len(list(selection.chunk_locations())) yield 0, f"Exporting {os.path.basename(path)}" for chunk_index, (cx, cz) in enumerate(selection.chunk_locations()): try: chunk = world.get_chunk(cx, cz, dimension) wrapper.commit_chunk(chunk, world.palette) except ChunkLoadError: continue yield (chunk_index + 1) / chunk_count wrapper.close() else: raise OperationError( "Please specify a save location and version in the options before running." )
def _operation( self, world: "World", dimension: Dimension, selection: SelectionGroup ) -> OperationReturnType: path = self._file_picker.GetPath() platform = self._version_define.platform version = self._version_define.version_number if isinstance(path, str) and platform and version: wrapper = ConstructionFormatWrapper(path) if wrapper.exists: response = wx.MessageDialog( self, f"A file is already present at {path}. Do you want to continue?", style=wx.YES | wx.NO, ).ShowModal() if response == wx.ID_CANCEL: return wrapper.create_and_open(platform, version, selection) wrapper.translation_manager = world.translation_manager wrapper_dimension = wrapper.dimensions[0] chunk_count = len(list(selection.chunk_locations())) yield 0, f"Exporting {os.path.basename(path)}" for chunk_index, (cx, cz) in enumerate(selection.chunk_locations()): try: chunk = world.get_chunk(cx, cz, dimension) wrapper.commit_chunk(chunk, wrapper_dimension) except ChunkLoadError: continue yield (chunk_index + 1) / chunk_count wrapper.save() wrapper.close() else: raise OperationError( "Please specify a save location and version in the options before running." )
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 __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, ) 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 export_construction(world: "World", dimension: Dimension, selection: SelectionGroup, options: dict): path, platform, version = options.get('path', None), options.get( 'platform', None), options.get('version', None) if isinstance( path, str) and path.endswith('.construction') and platform and version: wrapper = ConstructionFormatWrapper(path, 'w') wrapper.platform = platform wrapper.version = version wrapper.selection = selection wrapper.translation_manager = world.translation_manager wrapper.open() for cx, cz in selection.chunk_locations(): try: chunk = world.get_chunk(cx, cz, dimension) wrapper.commit_chunk(chunk, world.palette) except ChunkLoadError: continue wrapper.close() else: raise Exception( 'Please specify a save location and version in the options before running.' )
def prune_chunks(world: "BaseLevel", dimension: Dimension, selection: SelectionGroup): chunks = world.all_chunk_coords(dimension).difference( selection.chunk_locations()) for i, (cx, cz) in enumerate(chunks): world.delete_chunk(cx, cz, dimension) yield (i + 1) / len(chunks)
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 create_chunks( world: BaseLevel, dimension: Dimension, selection: SelectionGroup, ): for cx, cz in selection.chunk_locations(): if not world.has_chunk(cx, cz, dimension): world.put_chunk(Chunk(cx, cz), dimension)
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 _operation( self, world: "BaseLevel", dimension: Dimension, selection: SelectionGroup ) -> OperationReturnType: if len(selection.selection_boxes) == 0: raise OperationError("No selection was given to export.") elif len(selection.selection_boxes) != 1: raise OperationError( "The Sponge Schematic format only supports a single selection box." ) path = self._file_picker.GetPath() version = self._version_define.version_number if isinstance(path, str): wrapper = SpongeSchemFormatWrapper(path) if wrapper.exists: response = wx.MessageDialog( self, f"A file is already present at {path}. Do you want to continue?", style=wx.YES | wx.NO, ).ShowModal() if response == wx.ID_CANCEL: return wrapper.create_and_open("java", version, selection, True) wrapper.translation_manager = world.translation_manager wrapper_dimension = wrapper.dimensions[0] chunk_count = len(list(selection.chunk_locations())) yield 0, f"Exporting {os.path.basename(path)}" for chunk_index, (cx, cz) in enumerate(selection.chunk_locations()): try: chunk = world.get_chunk(cx, cz, dimension) wrapper.commit_chunk(chunk, wrapper_dimension) except ChunkLoadError: continue yield (chunk_index + 1) / chunk_count wrapper.save() wrapper.close() else: raise OperationError( "Please specify a save location and version in the options before running." )
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 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 _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_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 delete_chunk( world: "BaseLevel", dimension: Dimension, source_box: SelectionGroup, load_original: bool = True, ): chunks = [(cx, cz) for (cx, cz) in source_box.chunk_locations() if world.has_chunk(cx, cz, dimension)] iter_count = len(chunks) for count, (cx, cz) in enumerate(chunks): world.delete_chunk(cx, cz, dimension) if not load_original: # this part is kind of hacky. # Work out a propery API to do this. key = dimension, cx, cz if key not in world.chunks._history_database: world.chunks._register_original_entry(key, Chunk(cx, cz)) yield (count + 1) / iter_count
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 _operation(self, world: "World", dimension: Dimension, selection: SelectionGroup) -> OperationReturnType: path = self._file_picker.GetPath() platform = self._version_define.platform version = self._version_define.version if isinstance(path, str) and path.endswith('.construction') and platform and version: wrapper = ConstructionFormatWrapper(path, 'w') wrapper.platform = platform wrapper.version = version wrapper.selection = selection wrapper.translation_manager = world.translation_manager wrapper.open() for cx, cz in selection.chunk_locations(): try: chunk = world.get_chunk(cx, cz, dimension) wrapper.commit_chunk(chunk, world.palette) except ChunkLoadError: continue wrapper.close() else: raise OperationError('Please specify a save location and version in the options before running.')