def version(self, version: Tuple[int, int, int]):
     if self._open:
         log.error(
             "Construction version cannot be changed after the object has been opened."
         )
         return
     self._version = version
 def platform(self, platform: str):
     if self._open:
         log.error(
             "Construction platform cannot be changed after the object has been opened."
         )
         return
     self._platform = platform
Beispiel #3
0
    def get_chunk_boxes(
        self,
        dimension: Dimension,
        selection: Union[SelectionGroup, SelectionBox, None] = None,
        create_missing_chunks=False,
    ) -> Generator[Tuple[Chunk, SelectionBox], None, None]:
        """
        Given a selection will yield :class:`Chunk` and :class:`SelectionBox` instances into that chunk

        If not given a selection will use the bounds of the object.

        :param dimension: The dimension to take effect in.
        :param selection: SelectionGroup or SelectionBox into the level. If None will use :meth:`bounds` for the dimension.
        :param create_missing_chunks: If a chunk does not exist an empty one will be created (defaults to false). Use this with care.
        """
        for (cx, cz), box in self.get_coord_box(dimension, selection,
                                                create_missing_chunks):
            try:
                chunk = self.get_chunk(cx, cz, dimension)
            except ChunkDoesNotExist:
                if create_missing_chunks:
                    yield self.create_chunk(cx, cz, dimension), box
            except ChunkLoadError:
                log.error(f"Error loading chunk\n{traceback.format_exc()}")
            else:
                yield chunk, box
Beispiel #4
0
 def selection(self, selection: SelectionGroup):
     if self._open:
         log.error(
             "Construction selection cannot be changed after the object has been opened."
         )
         return
     self._selection = selection
Beispiel #5
0
def parse_export(plugin: dict, operations_dict: Dict[str, OperationLoader],
                 path: str):
    try:
        operations_dict[path] = OperationLoader(plugin, path)
    except OperationLoadException as e:
        log.error(f"Error loading plugin {path}. {e}")
    except Exception as e:
        log.error(f"Exception loading plugin {path}. {e}")
 def selection(self, selection: SelectionGroup):
     if self._open:
         log.error(
             "Construction selection cannot be changed after the object has been opened."
         )
         return
     if selection.selection_boxes:
         self._selection = selection.selection_boxes[0]
     else:
         raise Exception("Given selection box is empty")
Beispiel #7
0
 def value(self, value: Union[float, int, str]):
     if isinstance(value, float):
         self._value = value
     elif isinstance(value, int):
         self._value = float(value)
     elif isinstance(value, str) and value in states:
         self._value = states[value][1]
     else:
         log.error(
             f"Unrecognised chunk state {value}. Defaulting to fully generated.\nIf this is a new version report it to the developers."
         )
         self._value = 2.0
Beispiel #8
0
    def _load_obj(self, path: str) -> bool:
        if os.path.isdir(path):
            py_path = os.path.join(path, "__init__.py")
            if not os.path.isfile(py_path):
                return False
            import_path = ".".join(
                os.path.normpath(
                    os.path.relpath(
                        path, os.path.dirname(os.path.dirname(
                            amulet.__file__)))).split(os.sep))
            obj_name = os.path.basename(path)
        elif os.path.isfile(path):
            if not path.endswith(".py"):
                return False
            py_path = path
            import_path = ".".join(
                os.path.normpath(
                    os.path.relpath(
                        path[:-3],
                        os.path.dirname(os.path.dirname(
                            amulet.__file__)))).split(os.sep))
            obj_name = os.path.basename(path[:-3])
        else:
            return False

        with open(py_path) as f:
            first_line = f.readline()
        if first_line.strip() == f"# meta {self._object_type}":
            if obj_name in self._objects:
                log.error(
                    f"Multiple {self._object_type} classes with the name {obj_name}"
                )
                return False
            modu = importlib.import_module(import_path)

            if not hasattr(modu, "export"):
                log.error(
                    f'{self._object_type} "{obj_name}" is missing the export attribute'
                )
                return False

            c = getattr(modu, "export")
            if self._create_instance:
                self._objects[obj_name] = c()
            else:
                self._objects[obj_name] = c

            log.debug(f'Enabled {self._object_type} "{obj_name}"')
            return True
        return False
Beispiel #9
0
 def _load_obj(self, module_name: str):
     modu = importlib.import_module(module_name)
     if hasattr(modu, "export"):
         c = getattr(modu, "export")
         if issubclass(c, self._base_class):
             if self._create_instance:
                 self._objects[module_name] = c()
             else:
                 self._objects[module_name] = c
         else:
             log.error(
                 f"export for {module_name} must be a subclass of {self._base_class}"
             )
         log.debug(f'Enabled {self._object_type} "{module_name}"')
 def commit_chunk(self, chunk: "Chunk", *args):
     """
     Save a universal format chunk to the Format database (not the disk database)
     call save method to write changed chunks back to the disk database
     :param chunk: The chunk object to translate and save
     :return:
     """
     if not self.writeable:
         log.error("This object is not writeable")
         return
     try:
         self._commit_chunk(copy.deepcopy(chunk), *args)
     except Exception:
         log.error(f"Error saving chunk {chunk}", exc_info=True)
     self._changed = True
Beispiel #11
0
    def get_moved_chunk_slice_box(
        self,
        dimension: Dimension,
        destination_origin: BlockCoordinates,
        selection: Optional[Union[SelectionGroup, SelectionBox]] = None,
        destination_sub_chunk_shape: Optional[int] = None,
        create_missing_chunks: bool = False,
    ) -> Generator[Tuple[Chunk, 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 self.selection will end up
        :param selection: An optional selection. The overlap of this and self.selection will be used
        :param destination_sub_chunk_shape: the chunk shape of the destination object (defaults to self.sub_chunk_size)
        :param create_missing_chunks: Generate empty chunks if the chunk does not exist.
        :return:
        """
        for (
            (src_cx, src_cz),
                src_slices,
                src_box,
            (dst_cx, dst_cz),
                dst_slices,
                dst_box,
        ) in self.get_moved_coord_slice_box(
                dimension,
                destination_origin,
                selection,
                destination_sub_chunk_shape,
                create_missing_chunks,
        ):
            try:
                chunk = self.get_chunk(src_cx, src_cz, dimension)
            except ChunkDoesNotExist:
                chunk = self.create_chunk(dst_cx, dst_cz, dimension)
            except ChunkLoadError:
                log.error(f"Error loading chunk\n{traceback.format_exc()}")
                continue
            yield chunk, src_slices, src_box, (dst_cx,
                                               dst_cz), dst_slices, dst_box
    def load_chunk(self, cx: int, cz: int, *args) -> "Chunk":
        """
        Loads and creates a universal amulet.api.chunk.Chunk object from chunk coordinates.

        :param cx: The x coordinate of the chunk.
        :param cz: The z coordinate of the chunk.
        :return: The chunk at the given coordinates.
        """
        if not self.readable:
            raise ChunkLoadError("This object is not readable")
        try:
            return self._load_chunk(cx, cz, *args)
        except ChunkDoesNotExist as e:
            raise e
        except Exception:
            log.error(f"Error loading chunk {cx} {cz}", exc_info=True)
            raise ChunkLoadError
 def commit_chunk(self, chunk: "Chunk", dimension: Dimension):
     """
     Save a universal format chunk to the Format database (not the disk database)
     call save method to write changed chunks back to the disk database
     :param chunk: The chunk object to translate and save
     :param dimension: The dimension to commit the chunk to.
     :return:
     """
     try:
         self._verify_has_lock()
     except ObjectReadWriteError as e:
         log.error(e)
     try:
         self._commit_chunk(copy.deepcopy(chunk), dimension)
     except Exception:
         log.error(f"Error saving chunk {chunk}", exc_info=True)
     self._changed = True
 def _safe_load(
     self,
     meth: Callable,
     args: Tuple[Any, ...],
     msg: str,
     load_error: Type[EntryLoadError],
     does_not_exist_error: Type[EntryDoesNotExist],
 ):
     try:
         self._verify_has_lock()
     except ObjectReadWriteError as e:
         raise does_not_exist_error(e)
     try:
         return meth(*args)
     except does_not_exist_error as e:
         raise e
     except Exception as e:
         log.error(msg.format(*args), exc_info=True)
         raise load_error(e) from e
    def load_chunk(self, cx: int, cz: int, dimension: Dimension) -> "Chunk":
        """
        Loads and creates a universal amulet.api.chunk.Chunk object from chunk coordinates.

        :param cx: The x coordinate of the chunk.
        :param cz: The z coordinate of the chunk.
        :param dimension: The dimension to load the chunk from.
        :return: The chunk at the given coordinates.
        :raises: ChunkLoadError or ChunkDoesNotExist as is relevant.
        """
        try:
            self._verify_has_lock()
        except ObjectReadWriteError as e:
            raise ChunkLoadError(e)
        try:
            return self._load_chunk(cx, cz, dimension)
        except ChunkDoesNotExist as e:
            raise e
        except Exception as e:
            log.error(f"Error loading chunk {cx} {cz}", exc_info=True)
            raise ChunkLoadError(e)
Beispiel #16
0
    def value(self, value: Union[float, int, str]):
        """
        Set the status value.

        >>> chunk.status.value = 2
        >>> chunk.status.value = "full"

        This can be a float/int or a string. If it is a string it is looked up in the states look up table to find the float value it corresponds with.

        :param value: The value to set as the generation stage.
        """
        if isinstance(value, float):
            self._value = value
        elif isinstance(value, int):
            self._value = float(value)
        elif isinstance(value, str) and value in states:
            self._value = states[value][1]
        else:
            log.error(
                f"Unrecognised chunk state {value}. Defaulting to fully generated.\nIf this is a new version report it to the developers."
            )
            self._value = 2.0
Beispiel #17
0
 def _decode_biomes(self, chunk: Chunk, compound: TAG_Compound, floor_cy: int):
     biomes = compound.pop("Biomes", None)
     if isinstance(biomes, TAG_Int_Array):
         if (len(biomes) / 16) % 4:
             log.error(
                 f"The biome array size must be 4x4x4xN but got an array of size {biomes.value.size}"
             )
         else:
             arr = numpy.transpose(
                 biomes.astype(numpy.uint32).reshape((-1, 4, 4)),
                 (2, 0, 1),
             )  # YZX -> XYZ
             chunk.biomes = {
                 sy + floor_cy: arr
                 for sy, arr in enumerate(
                     numpy.split(
                         arr,
                         arr.shape[1] // 4,
                         1,
                     )
                 )
             }
Beispiel #18
0
    def _find(self):
        """Load all objects from the object directory"""

        directories = glob.iglob(os.path.join(glob.escape(self._directory),
                                              "**", ""),
                                 recursive=True)
        for d in directories:
            meta_path = os.path.join(d, f"{self._object_type}.meta")
            if not os.path.exists(meta_path):
                continue

            with open(meta_path) as fp:
                meta = json.load(fp)

            if meta["meta_version"] != self._supported_meta_version:
                log.error(
                    f'Could not enable {self._object_type} located in "{d}" due to unsupported meta version'
                )
                continue

            if (meta[self._object_type][f"{self._object_type}_version"] !=
                    self._supported_version):
                log.error(
                    f'Could not enable {self._object_type} "{meta[self._object_type]["id"]}" due to unsupported {self._object_type} version'
                )
                continue

            spec = importlib.util.spec_from_file_location(
                meta[self._object_type]["entry_point"],
                os.path.join(d,
                             meta[self._object_type]["entry_point"] + ".py"),
            )
            modu = importlib.util.module_from_spec(spec)
            spec.loader.exec_module(modu)

            if not hasattr(modu, f"{self._object_type.upper()}_CLASS"):
                log.error(
                    f'{self._object_type} "{meta[self._object_type]["id"]}" is missing the {self._object_type.upper()}_CLASS attribute'
                )
                continue

            c = getattr(modu, f"{self._object_type.upper()}_CLASS")
            if self._create_instance:
                self._loaded[meta[f"{self._object_type}"]["id"]] = c()
            else:
                self._loaded[meta[f"{self._object_type}"]["id"]] = c

            log.debug(
                f'Enabled {self._object_type} "{meta[self._object_type]["id"]}", version {meta[self._object_type]["wrapper_version"]}'
            )

        self._is_loaded = True
Beispiel #19
0
def load_format(directory: str,
                _format: str = None,
                forced: bool = False) -> "WorldFormatWrapper":
    """
    Loads the world located at the given directory with the appropriate format loader.

    :param directory: The directory of the world
    :param _format: The format name to use
    :param forced: Whether to force load the world even if incompatible
    :return: The loaded world
    """
    if not os.path.isdir(directory):
        if os.path.exists(directory):
            log.error(
                f'The path "{directory}" does exist but it is not a directory')
            raise Exception(
                f'The path "{directory}" does exist but it is not a directory')
        else:
            log.error(
                f'The path "{directory}" is not a valid path on this file system.'
            )
            raise Exception(
                f'The path "{directory}" is not a valid path on this file system.'
            )
    if _format is not None:
        if _format not in formats.loader:
            log.error(f"Could not find _format loader {_format}")
            raise FormatLoaderInvalidFormat(
                f"Could not find _format loader {_format}")
        if not forced and not formats.loader.identify(directory) == _format:
            log.error(f"{_format} is incompatible")
            raise FormatLoaderMismatched(f"{_format} is incompatible")
        format_class = formats.loader.get_by_id(_format)
    else:
        format_class = formats.loader.get(directory)
    return format_class(directory)
Beispiel #20
0
def replace(world: "BaseLevel", dimension: Dimension,
            selection: SelectionGroup, options: dict):
    original_blocks = options.get("original_blocks", None)
    if not isinstance(original_blocks, list) and all(
            isinstance(block, Block) for block in original_blocks):
        log.error(
            "Replace operation was not given a list of source Block objects")
        return

    replacement_blocks = options.get("replacement_blocks", None)
    if not isinstance(replacement_blocks, list) and all(
            isinstance(block, Block) for block in replacement_blocks):
        log.error(
            "Replace operation was not given a list of destination Block objects"
        )
        return
    original_blocks: List[Block]
    replacement_blocks: List[Block]

    if len(original_blocks) != len(replacement_blocks):
        if len(replacement_blocks) == 1:
            replacement_blocks = replacement_blocks * len(original_blocks)
        else:
            log.error(
                "Replace operation must be given the same number of destination blocks as source blocks"
            )

    original_internal_ids = list(
        map(world.block_palette.get_add_block, original_blocks))
    replacement_internal_ids = list(
        map(world.block_palette.get_add_block, replacement_blocks))

    for chunk, slices, _ in world.get_chunk_slice_box(dimension, selection):
        old_blocks = chunk.blocks[slices]
        new_blocks = old_blocks.copy()
        for original_id, replacement_id in zip(original_internal_ids,
                                               replacement_internal_ids):
            new_blocks[old_blocks == original_id] = replacement_id
        chunk.blocks[slices] = new_blocks
        chunk.changed = True
Beispiel #21
0
def _error(name, msg):
    log.error(f"Error loading plugin {name}. {msg}")
Beispiel #22
0
    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}"
            )
Beispiel #23
0
 def error(msg):
     log.error(
         f'Error loading plugin {os.path.basename(fpath)}. {msg}')
Beispiel #24
0
def _load_operations(path: str):
    if os.path.isdir(path):
        for fpath in glob.iglob(os.path.join(path, "*.py")):
            if fpath == __file__:
                continue
            mod = _load_module(fpath)
            if hasattr(mod, "export"):
                plugin = getattr(mod, "export")
                if not isinstance(plugin, dict):
                    log.error(f'Error loading plugin {os.path.basename(fpath)}. Export must be a dictionary.')
                    continue
                if not isinstance(plugin.get("name", None), str):
                    log.error(f'Error loading plugin {os.path.basename(fpath)}. "name" in export must exist and be a string.')
                    continue
                if not callable(plugin.get("operation", None)):
                    log.error(f'Error loading plugin {os.path.basename(fpath)}. "operation" in export must exist and be a function.')
                    continue
                inputs = plugin.get("inputs", [])
                if not isinstance(inputs, list):
                    log.error(f'Error loading plugin {os.path.basename(fpath)}. "inputs" in export must be a list.')
                    continue
                if "dst_box" in inputs or "dst_box_multiple" in inputs:
                    if "structure_callable" in plugin:
                        if not callable(plugin["structure_callable"]):
                            log.error(f'Error loading plugin {os.path.basename(fpath)}. "structure_callable" must be a callable if defined.')
                            continue
                    elif "src_box" not in inputs:
                        log.error(f'Error loading plugin {os.path.basename(fpath)}. "src_box" or "structure_callable" must be defined if "dst_box" or "dst_box_multiple" are.')
                        continue

                elif "structure" in inputs:
                    log.error(f'Error loading plugin {os.path.basename(fpath)}. "structure" cannot be defined if "dst_box" or "dst_box_multiple" are not.')
                    continue
                elif "structure_callable" in plugin:
                    log.error(f'Error loading plugin {os.path.basename(fpath)}. "structure_callable" cannot be defined if "dst_box" or "dst_box_multiple" are not.')
                    continue

                if "options" in inputs and "wxoptions" in inputs:
                    log.error(f'Error loading plugin {os.path.basename(fpath)}. Only one of "options" and "wxoptions" may be defined in "inputs" at once.')
                    continue
                elif "options" in inputs and "options" not in plugin:
                    log.error(f'Error loading plugin {os.path.basename(fpath)}. "options" was specificed in "inputs" but was not present in the dictionary.')
                    continue
                elif "wxoptions" in inputs and "wxoptions" not in plugin:
                    log.error(f'Error loading plugin {os.path.basename(fpath)}. "wxoptions" was specificed in "inputs" but was not present in the dictionary.')
                    continue

                if not all(v in _input_options for v in inputs):
                    for v in inputs:
                        if v not in _input_options:
                            log.error(f'Error loading plugin {os.path.basename(fpath)}. "{v}" is not a valid value in "inputs".')
                    continue

                operations[fpath] = plugin