def test_chain_transform2(self): numpy.testing.assert_array_equal( numpy.round( numpy.matmul( numpy.matmul( transform_matrix((1, 1, 1), (0, 0, 0), (10, 20, 30)), transform_matrix( (1, 1, 1), (0, numpy.deg2rad(90), 0), (0, 0, 0) ), ), (1, 1, 1, 1), )[:3], 1, ), (11, 21, 29), )
def test_inverse_transform(self): inputs = numpy.random.random(9).reshape((3, 3)).tolist() transform = transform_matrix(*inputs) inverse_transform = inverse_transform_matrix(*inputs) numpy.testing.assert_array_almost_equal( numpy.matmul(transform, inverse_transform), numpy.eye(4) )
def test_decompose(self): rotation_ = (1, 1.5, 3) m = rotation_matrix_xyz(*rotation_) scale, rotation, displacement = decompose_transformation_matrix(m) numpy.testing.assert_array_almost_equal( displacement, (0, 0, 0), err_msg="decomposed displacement is incorrect" ) numpy.testing.assert_array_almost_equal( scale, (1, 1, 1), err_msg="decomposed scale is incorrect" ) numpy.testing.assert_array_almost_equal( rotation, rotation_, err_msg="decomposed rotation is incorrect" ) m = transform_matrix((1, 2, 3), rotation_, (1, 2, 3)) scale, rotation, displacement = decompose_transformation_matrix(m) numpy.testing.assert_array_almost_equal( displacement, (1, 2, 3), err_msg="decomposed displacement is incorrect" ) numpy.testing.assert_array_almost_equal( scale, (1, 2, 3), err_msg="decomposed scale is incorrect" ) numpy.testing.assert_array_almost_equal( rotation, rotation_, err_msg="decomposed rotation is incorrect" )
def test_transform(self): numpy.testing.assert_array_equal( numpy.round( numpy.matmul( transform_matrix((1, 1, 1), (0, 0, 0), (10, 20, 30)), (1, 1, 1, 1) )[:3], 1, ), (11, 21, 31), )
def transform(self, scale: FloatTriplet, rotation: FloatTriplet) -> List[SelectionBox]: """creates a list of new transformed SelectionBox(es).""" boxes = [] # TODO: allow this to support rotations that are not 90 degrees min_point, max_point = numpy.matmul( transform_matrix((0, 0, 0), scale, rotation, "zyx"), numpy.array([[*self.min, 1], [*self.max, 1]]).T, ).T[:, :3] boxes.append(SelectionBox(min_point, max_point)) return boxes
def _mirror(self, axis: int): """Mirror the selection in the given axis. :param axis: The axis to scale in 0=x, 1=y, 2=z :return: """ scale = [(-1, 1, 1), (1, -1, 1), (1, 1, -1)][axis] self._scale.value, rotation, _ = decompose_transformation_matrix( numpy.matmul( scale_matrix(*scale), transform_matrix(self._scale.value, self._rotation_radians(), (0, 0, 0)), )) self._rotation.value = numpy.rad2deg(rotation) self._update_transform()
def transform_iter( self, scale: FloatTriplet, rotation: FloatTriplet) -> Generator[int, None, "Structure"]: """ creates a new transformed Structure class. :param scale: scale factor multiplier in the x, y and z directions :param rotation: rotation in degrees for pitch (y), yaw (z) and roll (x) :return: """ block_palette = copy.deepcopy(self.palette) rotation_radians = -numpy.flip(numpy.radians(rotation)) selection = self.selection.transform(scale, rotation_radians) transform = transform_matrix((0, 0, 0), scale, rotation_radians, "zyx") inverse_transform = numpy.linalg.inv(transform) chunks: Dict[ChunkCoordinates, Chunk] = {} volume = sum([box.volume for box in selection.selection_boxes]) index = 0 # TODO: find a way to do this without doing it block by block for box in selection.selection_boxes: coords = list(box.blocks()) coords_array = numpy.ones((len(coords), 4), dtype=numpy.float) coords_array[:, :3] = coords coords_array[:, :3] += 0.5 original_coords = (numpy.floor( numpy.matmul(inverse_transform, coords_array.T)).astype(int).T[:, :3]) for (x, y, z), (ox, oy, oz) in zip(coords, original_coords): cx, cz = chunk_key = (x >> 4, z >> 4) if chunk_key in chunks: chunk = chunks[chunk_key] else: chunk = chunks[chunk_key] = Chunk(cx, cz) chunk.block_palette = block_palette try: chunk.blocks[x % 16, y, z % 16] = self.get_chunk( ox >> 4, oz >> 4).blocks[ox % 16, oy, oz % 16] except ChunkDoesNotExist: pass yield index / volume index += 1 return Structure(chunks, block_palette, selection, self.chunk_size)
def paste_iter( world: "World", dimension: Dimension, structure: Structure, location: BlockCoordinates, scale: FloatTriplet, rotation: FloatTriplet, copy_air=True, copy_water=True, copy_lava=True, ): gab = numpy.vectorize(world.palette.get_add_block, otypes=[numpy.uint32]) lut = gab(structure.palette.blocks()) filtered_mode = not all([copy_air, copy_lava, copy_water]) filtered_blocks = [] if not copy_air: filtered_blocks.append("universal_minecraft:air") if not copy_water: filtered_blocks.append("universal_minecraft:water") if not copy_lava: filtered_blocks.append("universal_minecraft:lava") if filtered_mode: paste_blocks = numpy.array([ any(sub_block.namespaced_name not in filtered_blocks for sub_block in block.block_tuple) for block in structure.palette.blocks() ]) else: paste_blocks = None rotation_point = numpy.floor( (structure.selection.max + structure.selection.min) / 2).astype(int) if any(rotation) or any(s != 1 for s in scale): yield 0, "Rotating!" transformed_structure = yield from structure.transform_iter( scale, rotation) rotation_point = (numpy.matmul( transform_matrix((0, 0, 0), scale, -numpy.radians(numpy.flip(rotation)), "zyx"), numpy.array([*rotation_point, 1]), ).T[:3].round().astype(int)) else: transformed_structure = structure offset = location - rotation_point moved_min_location = transformed_structure.selection.min + offset iter_count = len( list(transformed_structure.get_moved_chunk_slices(moved_min_location))) count = 0 yield 0, "Pasting!" for ( src_chunk, src_slices, src_box, (dst_cx, dst_cz), dst_slices, dst_box, ) in transformed_structure.get_moved_chunk_slices(moved_min_location): try: dst_chunk = world.get_chunk(dst_cx, dst_cz, dimension) except ChunkDoesNotExist: dst_chunk = Chunk(dst_cx, dst_cz) world.put_chunk(dst_chunk, dimension) except ChunkLoadError: continue remove_block_entities = [] for block_entity_location in dst_chunk.block_entities.keys(): if block_entity_location in dst_box: if copy_air: remove_block_entities.append(block_entity_location) else: chunk_block_entity_location = ( numpy.array(block_entity_location) - offset) chunk_block_entity_location[[0, 2]] %= 16 if paste_blocks[src_chunk.blocks[tuple( chunk_block_entity_location)]]: remove_block_entities.append(block_entity_location) for block_entity_location in remove_block_entities: del dst_chunk.block_entities[block_entity_location] for block_entity_location, block_entity in src_chunk.block_entities.items( ): if block_entity_location in src_box: dst_chunk.block_entities.insert( block_entity.new_at_location(*offset + block_entity_location)) if not copy_air: # dst_blocks_copy = dst_chunk.blocks[dst_slices] # mask = paste_blocks[src_chunk.blocks[src_slices]] # dst_blocks_copy[mask] = lut[src_chunk.blocks[src_slices]][mask] dst_blocks_copy = numpy.asarray(dst_chunk.blocks[dst_slices]) mask = paste_blocks[src_chunk.blocks[src_slices]] dst_blocks_copy[mask] = lut[src_chunk.blocks[src_slices]][mask] dst_chunk.blocks[dst_slices] = dst_blocks_copy else: dst_chunk.blocks[dst_slices] = lut[src_chunk.blocks[src_slices]] dst_chunk.changed = True count += 1 yield count / iter_count
def transform(self, scale: FloatTriplet, rotation: FloatTriplet, translation: FloatTriplet) -> selection.SelectionGroup: """ Creates a :class:`~amulet.api.selection.SelectionGroup` of transformed SelectionBox(es). :param scale: A tuple of scaling factors in the x, y and z axis. :param rotation: The rotation about the x, y and z axis in radians. :param translation: The translation about the x, y and z axis. :return: A new :class:`~amulet.api.selection.SelectionGroup` representing the transformed selection. """ if all(r % 90 == 0 for r in rotation): min_point, max_point = numpy.matmul( transform_matrix(scale, rotation, translation), numpy.array([[*self.min, 1], [*self.max, 1]]).T, ).T[:, :3] return selection.SelectionGroup(SelectionBox(min_point, max_point)) else: boxes = [] for _, box, mask, _ in self._iter_transformed_boxes( transform_matrix(scale, rotation, translation)): if isinstance(mask, bool): if mask: boxes.append(box) else: box_shape = box.shape any_array: numpy.ndarray = numpy.any(mask, axis=2) box_2d_shape = numpy.array(any_array.shape) any_array_flat = any_array.ravel() start_array = numpy.argmax(mask, axis=2) stop_array = box_shape[2] - numpy.argmax( numpy.flip(mask, axis=2), axis=2) # effectively a greedy meshing algorithm in 2D index = 0 while index < any_array_flat.size: # while there are unhandled true values index = numpy.argmax(any_array_flat[index:]) + index # find the first true value if any_array_flat[index]: # check that that value is actually True # create the bounds for the box min_x, min_y = max_x, max_y = numpy.unravel_index( index, box_2d_shape) # find the z bounds min_z = start_array[min_x, min_y] max_z = stop_array[min_x, min_y] while max_x < box_2d_shape[0] - 1: # expand in the x while the bounds are the same new_max_x = max_x + 1 if (any_array[new_max_x, max_y] and start_array[new_max_x, max_y] == min_z and stop_array[new_max_x, max_y] == max_z): # the box z values are the same max_x = new_max_x else: break while max_y < box_2d_shape[1] - 1: # expand in the y while the bounds are the same new_max_y = max_y + 1 if (numpy.all(any_array[min_x:max_x + 1, new_max_y]) and numpy.all( start_array[min_x:max_x + 1, new_max_y] == min_z) and numpy.all( stop_array[min_x:max_x + 1, new_max_y] == max_z)): # the box z values are the same max_y = new_max_y else: break boxes.append( SelectionBox( box.min_array + (min_x, min_y, min_z), box.min_array + (max_x + 1, max_y + 1, max_z), )) any_array[min_x:max_x + 1, min_y:max_y + 1] = False else: # If there are no more True values argmax will return 0 break return selection.SelectionGroup(boxes)
def clone( src_structure: "BaseLevel", src_dimension: Dimension, src_selection: SelectionGroup, dst_structure: "BaseLevel", dst_dimension: Dimension, dst_selection_bounds: SelectionGroup, location: BlockCoordinates, scale: FloatTriplet = (1.0, 1.0, 1.0), rotation: FloatTriplet = (0.0, 0.0, 0.0), include_blocks: bool = True, include_entities: bool = True, skip_blocks: Tuple[Block, ...] = (), copy_chunk_not_exist: bool = False, ) -> Generator[float, None, None]: """Clone the source object data into the destination object with an optional transform. The src and dst can be the same object. Note this command may change in the future. Refer to all keyword arguments via the keyword. :param src_structure: The source structure to paste into the destination structure. :param src_dimension: The dimension of the source structure to use. :param src_selection: The area of the source structure to copy. :param dst_structure: The destination structure to paste into. :param dst_dimension: The dimension of the destination structure to use. :param dst_selection_bounds: The area of the destination structure that can be modified. :param location: The location where the centre of the `src_structure` will be in the `dst_structure` :param scale: The scale in the x, y and z axis. These can be negative to mirror. :param rotation: The rotation in degrees around each of the axis. :param include_blocks: Include blocks from the `src_structure`. :param include_entities: Include entities from the `src_structure`. :param skip_blocks: If a block matches a block in this list it will not be copied. :param copy_chunk_not_exist: If a chunk does not exist in the source should it be copied over as air. Always False where `src_structure` is a World. :return: A generator of floats from 0 to 1 with the progress of the paste operation. """ location = tuple(location) if include_blocks or include_entities: # we actually have to do something if isinstance(src_structure, amulet.api.level.World): copy_chunk_not_exist = False # TODO: look into if this can be a float so it will always be the exact middle rotation_point: numpy.ndarray = ( (src_selection.max + src_selection.min) // 2 ).astype(int) if src_structure is dst_structure and src_dimension == dst_dimension: # copying from an object to itself in the same dimension. # if the selections do not overlap this can be achieved directly # if they do overlap the selection will first need extracting # TODO: implement the above if ( tuple(rotation_point) == location and scale == (1.0, 1.0, 1.0) and rotation == (0.0, 0.0, 0.0) ): # The src_object was pasted into itself at the same location. Nothing will change so do nothing. return src_structure = src_structure.extract_structure( src_selection, src_dimension ) src_dimension = src_structure.dimensions[0] src_structure: "BaseLevel" # TODO: I don't know if this is feasible for large boxes: get the intersection of the source and destination selections and iterate over that to minimise work if any(rotation) or any(s != 1 for s in scale): rotation_radians = tuple(numpy.radians(rotation)) transform = numpy.matmul( transform_matrix(scale, rotation_radians, location), displacement_matrix(*-rotation_point), ) inverse_transform = numpy.linalg.inv(transform) dst_selection = ( src_selection.transform((1, 1, 1), (0, 0, 0), tuple(-rotation_point)) .transform(scale, rotation_radians, location) .intersection(dst_selection_bounds) ) volume = dst_selection.volume index = 0 last_src_cx: Optional[int] = None last_src_cz: Optional[int] = None src_chunk: Optional[ Chunk ] = None # None here means the chunk does not exist or failed to load. Treat it as if it was air. last_dst_cx: Optional[int] = None last_dst_cz: Optional[int] = None dst_chunk: Optional[ Chunk ] = None # None here means the chunk failed to load. Do not modify it. # TODO: find a way to do this without doing it block by block if include_blocks: for box in dst_selection.selection_boxes: dst_coords = list(box.blocks()) coords_array = numpy.ones((len(dst_coords), 4), dtype=numpy.float) coords_array[:, :3] = dst_coords coords_array[:, :3] += 0.5 src_coords = ( numpy.floor(numpy.matmul(inverse_transform, coords_array.T)) .astype(int) .T[:, :3] ) for (dst_x, dst_y, dst_z), (src_x, src_y, src_z) in zip( dst_coords, src_coords ): src_cx, src_cz = (src_x >> 4, src_z >> 4) if (src_cx, src_cz) != (last_src_cx, last_src_cz): last_src_cx = src_cx last_src_cz = src_cz try: src_chunk = src_structure.get_chunk( src_cx, src_cz, src_dimension ) except ChunkLoadError: src_chunk = None dst_cx, dst_cz = (dst_x >> 4, dst_z >> 4) if (dst_cx, dst_cz) != (last_dst_cx, last_dst_cz): last_dst_cx = dst_cx last_dst_cz = dst_cz try: dst_chunk = dst_structure.get_chunk( dst_cx, dst_cz, dst_dimension ) except ChunkDoesNotExist: dst_chunk = dst_structure.create_chunk( dst_cx, dst_cz, dst_dimension ) except ChunkLoadError: dst_chunk = None if dst_chunk is not None: if (dst_x, dst_y, dst_z) in dst_chunk.block_entities: del dst_chunk.block_entities[(dst_x, dst_y, dst_z)] if src_chunk is None: dst_chunk.blocks[ dst_x % 16, dst_y, dst_z % 16 ] = dst_chunk.block_palette.get_add_block( UniversalAirBlock ) else: # TODO implement support for individual block rotation dst_chunk.blocks[ dst_x % 16, dst_y, dst_z % 16 ] = dst_chunk.block_palette.get_add_block( src_chunk.block_palette[ src_chunk.blocks[src_x % 16, src_y, src_z % 16] ] ) if (src_x, src_y, src_z) in src_chunk.block_entities: dst_chunk.block_entities[ (dst_x, dst_y, dst_z) ] = src_chunk.block_entities[ (src_x, src_y, src_z) ].new_at_location( dst_x, dst_y, dst_z ) dst_chunk.changed = True yield index / volume index += 1 else: # the transform from the structure location to the world location offset = numpy.asarray(location).astype(int) - rotation_point moved_min_location = src_selection.min + offset iter_count = len( list( src_structure.get_moved_coord_slice_box( src_dimension, moved_min_location, src_selection, dst_structure.sub_chunk_size, yield_missing_chunks=copy_chunk_not_exist, ) ) ) count = 0 for ( src_chunk, src_slices, src_box, (dst_cx, dst_cz), dst_slices, dst_box, ) in src_structure.get_moved_chunk_slice_box( src_dimension, moved_min_location, src_selection, dst_structure.sub_chunk_size, create_missing_chunks=copy_chunk_not_exist, ): src_chunk: Chunk src_slices: Tuple[slice, slice, slice] src_box: SelectionBox dst_cx: int dst_cz: int dst_slices: Tuple[slice, slice, slice] dst_box: SelectionBox # load the destination chunk try: dst_chunk = dst_structure.get_chunk(dst_cx, dst_cz, dst_dimension) except ChunkDoesNotExist: dst_chunk = dst_structure.create_chunk( dst_cx, dst_cz, dst_dimension ) except ChunkLoadError: count += 1 continue if include_blocks: # a boolean array specifying if each index should be pasted. paste_blocks = gen_paste_blocks( src_chunk.block_palette, skip_blocks ) # create a look up table converting the source block ids to the destination block ids gab = numpy.vectorize( dst_chunk.block_palette.get_add_block, otypes=[numpy.uint32] ) lut = gab(src_chunk.block_palette.blocks()) # iterate through all block entities in the chunk and work out if the block is going to be overwritten remove_block_entities = [] for block_entity_location in dst_chunk.block_entities.keys(): if block_entity_location in dst_box: chunk_block_entity_location = ( numpy.array(block_entity_location) - offset ) chunk_block_entity_location[[0, 2]] %= 16 if paste_blocks[ src_chunk.blocks[tuple(chunk_block_entity_location)] ]: remove_block_entities.append(block_entity_location) for block_entity_location in remove_block_entities: del dst_chunk.block_entities[block_entity_location] # copy over the source block entities if the source block is supposed to be pasted for ( block_entity_location, block_entity, ) in src_chunk.block_entities.items(): if block_entity_location in src_box: chunk_block_entity_location = numpy.array( block_entity_location ) chunk_block_entity_location[[0, 2]] %= 16 if paste_blocks[ src_chunk.blocks[tuple(chunk_block_entity_location)] ]: dst_chunk.block_entities.insert( block_entity.new_at_location( *offset + block_entity_location ) ) mask = paste_blocks[src_chunk.blocks[src_slices]] dst_chunk.blocks[dst_slices][mask] = lut[ src_chunk.blocks[src_slices] ][mask] dst_chunk.changed = True if include_entities: # TODO: implement pasting entities when we support entities pass count += 1 yield count / iter_count yield 1.0
def clone( src_structure: "BaseLevel", src_dimension: Dimension, src_selection: SelectionGroup, dst_structure: "BaseLevel", dst_dimension: Dimension, dst_selection_bounds: SelectionGroup, location: BlockCoordinates, scale: FloatTriplet = (1.0, 1.0, 1.0), rotation: FloatTriplet = (0.0, 0.0, 0.0), include_blocks: bool = True, include_entities: bool = True, skip_blocks: Tuple[Block, ...] = (), copy_chunk_not_exist: bool = False, ) -> Generator[float, None, None]: """Clone the source object data into the destination object with an optional transform. The src and dst can be the same object. Note this command may change in the future. Refer to all keyword arguments via the keyword. :param src_structure: The source structure to paste into the destination structure. :param src_dimension: The dimension of the source structure to use. :param src_selection: The area of the source structure to copy. :param dst_structure: The destination structure to paste into. :param dst_dimension: The dimension of the destination structure to use. :param dst_selection_bounds: The area of the destination structure that can be modified. :param location: The location where the centre of the `src_structure` will be in the `dst_structure` :param scale: The scale in the x, y and z axis. These can be negative to mirror. :param rotation: The rotation in degrees around each of the axis. :param include_blocks: Include blocks from the `src_structure`. :param include_entities: Include entities from the `src_structure`. :param skip_blocks: If a block matches a block in this list it will not be copied. :param copy_chunk_not_exist: If a chunk does not exist in the source should it be copied over as air. Always False where `src_structure` is a World. :return: A generator of floats from 0 to 1 with the progress of the paste operation. """ location = tuple(location) src_selection = src_selection.merge_boxes() if include_blocks or include_entities: # we actually have to do something if isinstance(src_structure, amulet.api.level.World): copy_chunk_not_exist = False # TODO: look into if this can be a float so it will always be the exact middle rotation_point: numpy.ndarray = ( (src_selection.max_array + src_selection.min_array) // 2).astype(int) if src_structure is dst_structure and src_dimension == dst_dimension: # copying from an object to itself in the same dimension. # if the selections do not overlap this can be achieved directly # if they do overlap the selection will first need extracting # TODO: implement the above if (tuple(rotation_point) == location and scale == (1.0, 1.0, 1.0) and rotation == (0.0, 0.0, 0.0)): # The src_object was pasted into itself at the same location. Nothing will change so do nothing. return src_structure = src_structure.extract_structure( src_selection, src_dimension) src_dimension = src_structure.dimensions[0] src_structure: "BaseLevel" # TODO: I don't know if this is feasible for large boxes: get the intersection of the source and destination selections and iterate over that to minimise work if any(rotation) or any(s != 1 for s in scale): rotation_radians = tuple(numpy.radians(rotation)) transform = numpy.matmul( transform_matrix(scale, rotation_radians, location), displacement_matrix(*-rotation_point), ) last_src: Optional[Tuple[int, int]] = None src_chunk: Optional[ Chunk] = None # None here means the chunk does not exist or failed to load. Treat it as if it was air. last_dst: Optional[Tuple[int, int]] = None dst_chunk: Optional[ Chunk] = None # None here means the chunk failed to load. Do not modify it. sum_progress = 0 volumes = tuple(box.sub_chunk_count() for box in src_selection.selection_boxes) sum_volumes = sum(volumes) volumes = tuple(vol / sum_volumes for vol in volumes) if include_blocks: blocks_to_skip = set(skip_blocks) for box_index, box in enumerate(src_selection.selection_boxes): for progress, src_coords, dst_coords in box.transformed_points( transform): if src_coords is not None: dst_cx, dst_cy, dst_cz = dst_coords[0] >> 4 if (dst_cx, dst_cz) != last_dst: last_dst = dst_cx, dst_cz try: dst_chunk = dst_structure.get_chunk( dst_cx, dst_cz, dst_dimension) except ChunkDoesNotExist: dst_chunk = dst_structure.create_chunk( dst_cx, dst_cz, dst_dimension) except ChunkLoadError: dst_chunk = None src_coords = numpy.floor(src_coords).astype(int) # due to how the coords are found dst_coords will all be in the same sub-chunk src_chunk_coords = src_coords >> 4 # split the src coords into which sub-chunks they came from unique_chunks, inverse, counts = numpy.unique( src_chunk_coords, return_inverse=True, return_counts=True, axis=0, ) chunk_indexes = numpy.argsort(inverse) src_block_locations = numpy.split( src_coords[chunk_indexes], numpy.cumsum(counts)[:-1]) dst_block_locations = numpy.split( dst_coords[chunk_indexes], numpy.cumsum(counts)[:-1]) for chunk_location, src_blocks, dst_blocks in zip( unique_chunks, src_block_locations, dst_block_locations): # for each src sub-chunk src_cx, src_cy, src_cz = chunk_location if (src_cx, src_cz) != last_src: last_src = src_cx, src_cz try: src_chunk = src_structure.get_chunk( src_cx, src_cz, src_dimension) except ChunkLoadError: src_chunk = None if dst_chunk is not None: if (src_chunk is not None and src_cy in src_chunk.blocks): # TODO implement support for individual block rotation block_ids = src_chunk.blocks.get_sub_chunk( src_cy)[tuple(src_blocks.T % 16)] for block_id in numpy.unique( block_ids): block = src_chunk.block_palette[ block_id] if not is_sub_block( skip_blocks, block): mask = block_ids == block_id dst_blocks_ = dst_blocks[mask] dst_chunk.blocks.get_sub_chunk( dst_cy )[tuple( dst_blocks_.T % 16 )] = dst_chunk.block_palette.get_add_block( block) src_blocks_ = src_blocks[mask] for src_location, dst_location in zip( src_blocks_, dst_blocks_): src_location = tuple( src_location.tolist()) dst_location = tuple( dst_location.tolist()) if (src_location in src_chunk. block_entities): dst_chunk.block_entities[ dst_location] = src_chunk.block_entities[ src_location].new_at_location( * dst_location ) elif (dst_location in dst_chunk. block_entities): del dst_chunk.block_entities[ dst_location] dst_chunk.changed = True elif UniversalAirBlock not in blocks_to_skip: dst_chunk.blocks.get_sub_chunk( dst_cy )[tuple( dst_blocks.T % 16 )] = dst_chunk.block_palette.get_add_block( UniversalAirBlock) for location in dst_blocks: location = tuple(location.tolist()) if location in dst_chunk.block_entities: del dst_chunk.block_entities[ location] dst_chunk.changed = True yield sum_progress + volumes[box_index] * progress sum_progress += volumes[box_index] else: # the transform from the structure location to the world location offset = numpy.asarray(location).astype(int) - rotation_point moved_min_location = src_selection.min_array + offset iter_count = len( list( src_structure.get_moved_coord_slice_box( src_dimension, moved_min_location, src_selection, dst_structure.sub_chunk_size, yield_missing_chunks=copy_chunk_not_exist, ))) count = 0 for ( src_chunk, src_slices, src_box, (dst_cx, dst_cz), dst_slices, dst_box, ) in src_structure.get_moved_chunk_slice_box( src_dimension, moved_min_location, src_selection, dst_structure.sub_chunk_size, create_missing_chunks=copy_chunk_not_exist, ): src_chunk: Chunk src_slices: Tuple[slice, slice, slice] src_box: SelectionBox dst_cx: int dst_cz: int dst_slices: Tuple[slice, slice, slice] dst_box: SelectionBox # load the destination chunk try: dst_chunk = dst_structure.get_chunk( dst_cx, dst_cz, dst_dimension) except ChunkDoesNotExist: dst_chunk = dst_structure.create_chunk( dst_cx, dst_cz, dst_dimension) except ChunkLoadError: count += 1 continue if include_blocks: # a boolean array specifying if each index should be pasted. paste_blocks = gen_paste_blocks(src_chunk.block_palette, skip_blocks) # create a look up table converting the source block ids to the destination block ids gab = numpy.vectorize( dst_chunk.block_palette.get_add_block, otypes=[numpy.uint32]) lut = gab(src_chunk.block_palette.blocks) # iterate through all block entities in the chunk and work out if the block is going to be overwritten remove_block_entities = [] for block_entity_location in dst_chunk.block_entities.keys( ): if block_entity_location in dst_box: chunk_block_entity_location = ( numpy.array(block_entity_location) - offset) chunk_block_entity_location[[0, 2]] %= 16 if paste_blocks[src_chunk.blocks[tuple( chunk_block_entity_location)]]: remove_block_entities.append( block_entity_location) for block_entity_location in remove_block_entities: del dst_chunk.block_entities[block_entity_location] # copy over the source block entities if the source block is supposed to be pasted for ( block_entity_location, block_entity, ) in src_chunk.block_entities.items(): if block_entity_location in src_box: chunk_block_entity_location = numpy.array( block_entity_location) chunk_block_entity_location[[0, 2]] %= 16 if paste_blocks[src_chunk.blocks[tuple( chunk_block_entity_location)]]: dst_chunk.block_entities.insert( block_entity.new_at_location( *offset + block_entity_location)) try: block_mask = src_chunk.blocks[src_slices] mask = paste_blocks[block_mask] dst_chunk.blocks[dst_slices][mask] = lut[ src_chunk.blocks[src_slices]][mask] dst_chunk.changed = True except IndexError as e: locals_copy = locals().copy() import traceback numpy_threshold = numpy.get_printoptions()["threshold"] numpy.set_printoptions(threshold=sys.maxsize) with open("clone_error.log", "w") as f: for k, v in locals_copy.items(): f.write(f"{k}: {v}\n\n") numpy.set_printoptions(threshold=numpy_threshold) raise IndexError( f"Error pasting.\nPlease notify the developers and include the clone_error.log file.\n{e}" ) from e if include_entities: # TODO: implement pasting entities when we support entities pass count += 1 yield count / iter_count yield 1.0