diff --git a/pytiled_parser/__init__.py b/pytiled_parser/__init__.py index 46255ba..a58b02f 100644 --- a/pytiled_parser/__init__.py +++ b/pytiled_parser/__init__.py @@ -13,7 +13,8 @@ PyTiled Parser is not tied to any particular graphics library or game engine. from .common_types import OrderedPair, Size from .layer import ImageLayer, Layer, LayerGroup, ObjectLayer, TileLayer +from .parser import parse_map from .properties import Properties -from .tiled_map import TiledMap, parse_map +from .tiled_map import TiledMap from .tileset import Tile, Tileset from .version import __version__ diff --git a/pytiled_parser/layer.py b/pytiled_parser/layer.py index a0237b2..8a3bc9b 100644 --- a/pytiled_parser/layer.py +++ b/pytiled_parser/layer.py @@ -8,27 +8,14 @@ See: # pylint: disable=too-few-public-methods -import base64 -import gzip -import importlib.util -import zlib from pathlib import Path -from typing import Any, List, Optional, Union -from typing import cast as type_cast +from typing import List, Optional, Union import attr -from typing_extensions import TypedDict -from . import properties as properties_ -from . import tiled_object -from .common_types import Color, OrderedPair, Size -from .util import parse_color - -zstd_spec = importlib.util.find_spec("zstd") -if zstd_spec: - import zstd # pylint: disable=import-outside-toplevel -else: - zstd = None # pylint: disable=invalid-name +from pytiled_parser.common_types import Color, OrderedPair, Size +from pytiled_parser.properties import Properties +from pytiled_parser.tiled_object import TiledObject @attr.s(auto_attribs=True, kw_only=True) @@ -60,7 +47,7 @@ class Layer: id: Optional[int] = None size: Optional[Size] = None - properties: Optional[properties_.Properties] = None + properties: Optional[Properties] = None tint_color: Optional[Color] = None @@ -127,7 +114,7 @@ class ObjectLayer(Layer): for more info. """ - tiled_objects: List[tiled_object.TiledObject] + tiled_objects: List[TiledObject] draw_order: Optional[str] = "topdown" @@ -162,341 +149,3 @@ class LayerGroup(Layer): """ layers: Optional[List[Layer]] - - -class RawChunk(TypedDict): - """The keys and their types that appear in a Chunk JSON Object. - - See: https://doc.mapeditor.org/en/stable/reference/json-map-format/#chunk - """ - - data: Union[List[int], str] - height: int - width: int - x: int - y: int - - -class RawLayer(TypedDict): - """The keys and their types that appear in a Layer JSON Object. - - See: https://doc.mapeditor.org/en/stable/reference/json-map-format/#layer - """ - - chunks: List[RawChunk] - compression: str - data: Union[List[int], str] - draworder: str - encoding: str - height: int - id: int - image: str - layers: List[Any] - name: str - objects: List[tiled_object.RawTiledObject] - offsetx: float - offsety: float - parallaxx: float - parallaxy: float - opacity: float - properties: List[properties_.RawProperty] - startx: int - starty: int - tintcolor: str - transparentcolor: str - type: str - visible: bool - width: int - x: int - y: int - - -def _convert_raw_tile_layer_data(data: List[int], layer_width: int) -> List[List[int]]: - """Convert raw layer data into a nested lit based on the layer width - - Args: - data: The data to convert - layer_width: Width of the layer - - Returns: - List[List[int]]: A nested list containing the converted data - """ - tile_grid: List[List[int]] = [[]] - - column_count = 0 - row_count = 0 - for item in data: - column_count += 1 - tile_grid[row_count].append(item) - if not column_count % layer_width and column_count < len(data): - row_count += 1 - tile_grid.append([]) - - return tile_grid - - -def _decode_tile_layer_data( - data: str, compression: str, layer_width: int -) -> List[List[int]]: - """Decode Base64 Encoded tile data. Optionally supports gzip and zlib compression. - - Args: - data: The base64 encoded data - compression: Either zlib, gzip, or empty. If empty no decompression is done. - - Returns: - List[List[int]]: A nested list containing the decoded data - - Raises: - ValueError: For an unsupported compression type. - """ - unencoded_data = base64.b64decode(data) - if compression == "zlib": - unzipped_data = zlib.decompress(unencoded_data) - elif compression == "gzip": - unzipped_data = gzip.decompress(unencoded_data) - elif compression == "zstd" and zstd is None: - raise ValueError( - "zstd compression support is not installed." - "To install use 'pip install pytiled-parser[zstd]'" - ) - elif compression == "zstd": - unzipped_data = zstd.decompress(unencoded_data) - else: - unzipped_data = unencoded_data - - tile_grid: List[int] = [] - - byte_count = 0 - int_count = 0 - int_value = 0 - for byte in unzipped_data: - int_value += byte << (byte_count * 8) - byte_count += 1 - if not byte_count % 4: - byte_count = 0 - int_count += 1 - tile_grid.append(int_value) - int_value = 0 - - return _convert_raw_tile_layer_data(tile_grid, layer_width) - - -def _cast_chunk( - raw_chunk: RawChunk, - encoding: Optional[str] = None, - compression: Optional[str] = None, -) -> Chunk: - """Cast the raw_chunk to a Chunk. - - Args: - raw_chunk: RawChunk to be casted to a Chunk - encoding: Encoding type. ("base64" or None) - compression: Either zlib, gzip, or empty. If empty no decompression is done. - - Returns: - Chunk: The Chunk created from the raw_chunk - """ - if encoding == "base64": - assert isinstance(compression, str) - assert isinstance(raw_chunk["data"], str) - data = _decode_tile_layer_data( - raw_chunk["data"], compression, raw_chunk["width"] - ) - else: - data = _convert_raw_tile_layer_data( - raw_chunk["data"], raw_chunk["width"] # type: ignore - ) - - chunk = Chunk( - coordinates=OrderedPair(raw_chunk["x"], raw_chunk["y"]), - size=Size(raw_chunk["width"], raw_chunk["height"]), - data=data, - ) - - return chunk - - -def _get_common_attributes(raw_layer: RawLayer) -> Layer: - """Create a Layer containing all the attributes common to all layers. - - This is to create the stub Layer object that can then be used to create the actual - specific sub-classes of Layer. - - Args: - raw_layer: Raw Tiled object get common attributes from - - Returns: - Layer: The attributes in common of all layers - """ - common_attributes = Layer( - name=raw_layer["name"], - opacity=raw_layer["opacity"], - visible=raw_layer["visible"], - ) - - # if startx is present, starty is present - if raw_layer.get("startx") is not None: - common_attributes.coordinates = OrderedPair( - raw_layer["startx"], raw_layer["starty"] - ) - - if raw_layer.get("id") is not None: - common_attributes.id = raw_layer["id"] - - # if either width or height is present, they both are - if raw_layer.get("width") is not None: - common_attributes.size = Size(raw_layer["width"], raw_layer["height"]) - - if raw_layer.get("offsetx") is not None: - common_attributes.offset = OrderedPair( - raw_layer["offsetx"], raw_layer["offsety"] - ) - - if raw_layer.get("properties") is not None: - common_attributes.properties = properties_.cast(raw_layer["properties"]) - - parallax = [1.0, 1.0] - - if raw_layer.get("parallaxx") is not None: - parallax[0] = raw_layer["parallaxx"] - - if raw_layer.get("parallaxy") is not None: - parallax[1] = raw_layer["parallaxy"] - - common_attributes.parallax_factor = OrderedPair(parallax[0], parallax[1]) - - if raw_layer.get("tintcolor") is not None: - common_attributes.tint_color = parse_color(raw_layer["tintcolor"]) - - return common_attributes - - -def _cast_tile_layer(raw_layer: RawLayer) -> TileLayer: - """Cast the raw_layer to a TileLayer. - - Args: - raw_layer: RawLayer to be casted to a TileLayer - - Returns: - TileLayer: The TileLayer created from raw_layer - """ - tile_layer = TileLayer(**_get_common_attributes(raw_layer).__dict__) - - if raw_layer.get("chunks") is not None: - tile_layer.chunks = [] - for chunk in raw_layer["chunks"]: - if raw_layer.get("encoding") is not None: - tile_layer.chunks.append( - _cast_chunk(chunk, raw_layer["encoding"], raw_layer["compression"]) - ) - else: - tile_layer.chunks.append(_cast_chunk(chunk)) - - if raw_layer.get("data") is not None: - if raw_layer.get("encoding") is not None: - tile_layer.data = _decode_tile_layer_data( - data=type_cast(str, raw_layer["data"]), - compression=raw_layer["compression"], - layer_width=raw_layer["width"], - ) - else: - tile_layer.data = _convert_raw_tile_layer_data( - raw_layer["data"], raw_layer["width"] # type: ignore - ) - - return tile_layer - - -def _cast_object_layer( - raw_layer: RawLayer, - parent_dir: Optional[Path] = None, -) -> ObjectLayer: - """Cast the raw_layer to an ObjectLayer. - - Args: - raw_layer: RawLayer to be casted to an ObjectLayer - Returns: - ObjectLayer: The ObjectLayer created from raw_layer - """ - - tiled_objects = [] - for tiled_object_ in raw_layer["objects"]: - tiled_objects.append(tiled_object.cast(tiled_object_, parent_dir)) - - return ObjectLayer( - tiled_objects=tiled_objects, - draw_order=raw_layer["draworder"], - **_get_common_attributes(raw_layer).__dict__, - ) - - -def _cast_image_layer(raw_layer: RawLayer) -> ImageLayer: - """Cast the raw_layer to a ImageLayer. - - Args: - raw_layer: RawLayer to be casted to a ImageLayer - - Returns: - ImageLayer: The ImageLayer created from raw_layer - """ - image_layer = ImageLayer( - image=Path(raw_layer["image"]), **_get_common_attributes(raw_layer).__dict__ - ) - - if raw_layer.get("transparentcolor") is not None: - image_layer.transparent_color = parse_color(raw_layer["transparentcolor"]) - - return image_layer - - -def _cast_group_layer( - raw_layer: RawLayer, parent_dir: Optional[Path] = None -) -> LayerGroup: - """Cast the raw_layer to a LayerGroup. - - Args: - raw_layer: RawLayer to be casted to a LayerGroup - - Returns: - LayerGroup: The LayerGroup created from raw_layer - """ - - layers = [] - - for layer in raw_layer["layers"]: - layers.append(cast(layer, parent_dir=parent_dir)) - - return LayerGroup(layers=layers, **_get_common_attributes(raw_layer).__dict__) - - -def cast( - raw_layer: RawLayer, - parent_dir: Optional[Path] = None, -) -> Layer: - """Cast a raw Tiled layer into a pytiled_parser type. - - This function will determine the type of layer and cast accordingly. - - Args: - raw_layer: Raw layer to be cast. - parent_dir: The parent directory that the map file is in. - - Returns: - Layer: a properly typed Layer. - - Raises: - RuntimeError: For an invalid layer type being provided - """ - type_ = raw_layer["type"] - - if type_ == "objectgroup": - return _cast_object_layer(raw_layer, parent_dir) - elif type_ == "group": - return _cast_group_layer(raw_layer, parent_dir) - elif type_ == "imagelayer": - return _cast_image_layer(raw_layer) - elif type_ == "tilelayer": - return _cast_tile_layer(raw_layer) - - raise RuntimeError(f"An invalid layer type of {type_} was supplied") diff --git a/pytiled_parser/parser.py b/pytiled_parser/parser.py new file mode 100644 index 0000000..7cdd443 --- /dev/null +++ b/pytiled_parser/parser.py @@ -0,0 +1,17 @@ +from pathlib import Path + +from pytiled_parser.parsers.json.tiled_map import parse as json_map_parse +from pytiled_parser.tiled_map import TiledMap + + +def parse_map(file: Path) -> TiledMap: + """Parse the raw Tiled map into a pytiled_parser type + + Args: + file: Path to the map's JSON file + + Returns: + TileSet: a properly typed TileSet. + """ + # I have no idea why, but mypy thinks this function returns "Any" + return json_map_parse(file) # type: ignore diff --git a/pytiled_parser/parsers/json/layer.py b/pytiled_parser/parsers/json/layer.py new file mode 100644 index 0000000..4953045 --- /dev/null +++ b/pytiled_parser/parsers/json/layer.py @@ -0,0 +1,364 @@ +"""Layer parsing for the JSON Map Format. +""" +import base64 +import gzip +import importlib.util +import zlib +from pathlib import Path +from typing import Any, List, Optional, Union, cast + +from typing_extensions import TypedDict + +from pytiled_parser.common_types import OrderedPair, Size +from pytiled_parser.layer import ( + Chunk, + ImageLayer, + Layer, + LayerGroup, + ObjectLayer, + TileLayer, +) +from pytiled_parser.parsers.json.properties import RawProperty +from pytiled_parser.parsers.json.properties import parse as parse_properties +from pytiled_parser.parsers.json.tiled_object import RawObject +from pytiled_parser.parsers.json.tiled_object import parse as parse_object +from pytiled_parser.util import parse_color + +zstd_spec = importlib.util.find_spec("zstd") +if zstd_spec: + import zstd +else: + zstd = None + + +class RawChunk(TypedDict): + """The keys and their types that appear in a Tiled JSON Chunk Object. + + Tiled Doc: https://doc.mapeditor.org/en/stable/reference/json-map-format/#chunk + """ + + data: Union[List[int], str] + height: int + width: int + x: int + y: int + + +class RawLayer(TypedDict): + """The keys and their types that appear in a Tiled JSON Layer Object. + + Tiled Doc: https://doc.mapeditor.org/en/stable/reference/json-map-format/#layer + """ + + chunks: List[RawChunk] + compression: str + data: Union[List[int], str] + draworder: str + encoding: str + height: int + id: int + image: str + layers: List[Any] + name: str + objects: List[RawObject] + offsetx: float + offsety: float + parallaxx: float + parallaxy: float + opacity: float + properties: List[RawProperty] + startx: int + starty: int + tintcolor: str + transparentcolor: str + type: str + visible: bool + width: int + x: int + y: int + + +def _convert_raw_tile_layer_data(data: List[int], layer_width: int) -> List[List[int]]: + """Convert raw layer data into a nested lit based on the layer width + + Args: + data: The data to convert + layer_width: Width of the layer + + Returns: + List[List[int]]: A nested list containing the converted data + """ + tile_grid: List[List[int]] = [[]] + + column_count = 0 + row_count = 0 + for item in data: + column_count += 1 + tile_grid[row_count].append(item) + if not column_count % layer_width and column_count < len(data): + row_count += 1 + tile_grid.append([]) + + return tile_grid + + +def _decode_tile_layer_data( + data: str, compression: str, layer_width: int +) -> List[List[int]]: + """Decode Base64 Encoded tile data. Optionally supports gzip and zlib compression. + + Args: + data: The base64 encoded data + compression: Either zlib, gzip, or empty. If empty no decompression is done. + + Returns: + List[List[int]]: A nested list containing the decoded data + + Raises: + ValueError: For an unsupported compression type. + """ + unencoded_data = base64.b64decode(data) + if compression == "zlib": + unzipped_data = zlib.decompress(unencoded_data) + elif compression == "gzip": + unzipped_data = gzip.decompress(unencoded_data) + elif compression == "zstd" and zstd is None: + raise ValueError( + "zstd compression support is not installed." + "To install use 'pip install pytiled-parser[zstd]'" + ) + elif compression == "zstd": + unzipped_data = zstd.decompress(unencoded_data) + else: + unzipped_data = unencoded_data + + tile_grid: List[int] = [] + + byte_count = 0 + int_count = 0 + int_value = 0 + for byte in unzipped_data: + int_value += byte << (byte_count * 8) + byte_count += 1 + if not byte_count % 4: + byte_count = 0 + int_count += 1 + tile_grid.append(int_value) + int_value = 0 + + return _convert_raw_tile_layer_data(tile_grid, layer_width) + + +def _parse_chunk( + raw_chunk: RawChunk, + encoding: Optional[str] = None, + compression: Optional[str] = None, +) -> Chunk: + """Parse the raw_chunk to a Chunk. + + Args: + raw_chunk: RawChunk to be parsed to a Chunk + encoding: Encoding type. ("base64" or None) + compression: Either zlib, gzip, or empty. If empty no decompression is done. + + Returns: + Chunk: The Chunk created from the raw_chunk + """ + if encoding == "base64": + assert isinstance(compression, str) + assert isinstance(raw_chunk["data"], str) + data = _decode_tile_layer_data( + raw_chunk["data"], compression, raw_chunk["width"] + ) + else: + data = _convert_raw_tile_layer_data( + raw_chunk["data"], raw_chunk["width"] # type: ignore + ) + + chunk = Chunk( + coordinates=OrderedPair(raw_chunk["x"], raw_chunk["y"]), + size=Size(raw_chunk["width"], raw_chunk["height"]), + data=data, + ) + + return chunk + + +def _parse_common(raw_layer: RawLayer) -> Layer: + """Create a Layer containing all the attributes common to all layer types. + + This is to create the stub Layer object that can then be used to create the actual + specific sub-classes of Layer. + + Args: + raw_layer: Raw layer get common attributes from + + Returns: + Layer: The attributes in common of all layer types + """ + common = Layer( + name=raw_layer["name"], + opacity=raw_layer["opacity"], + visible=raw_layer["visible"], + ) + + # if startx is present, starty is present + if raw_layer.get("startx") is not None: + common.coordinates = OrderedPair(raw_layer["startx"], raw_layer["starty"]) + + if raw_layer.get("id") is not None: + common.id = raw_layer["id"] + + # if either width or height is present, they both are + if raw_layer.get("width") is not None: + common.size = Size(raw_layer["width"], raw_layer["height"]) + + if raw_layer.get("offsetx") is not None: + common.offset = OrderedPair(raw_layer["offsetx"], raw_layer["offsety"]) + + if raw_layer.get("properties") is not None: + common.properties = parse_properties(raw_layer["properties"]) + + parallax = [1.0, 1.0] + + if raw_layer.get("parallaxx") is not None: + parallax[0] = raw_layer["parallaxx"] + + if raw_layer.get("parallaxy") is not None: + parallax[1] = raw_layer["parallaxy"] + + common.parallax_factor = OrderedPair(parallax[0], parallax[1]) + + if raw_layer.get("tintcolor") is not None: + common.tint_color = parse_color(raw_layer["tintcolor"]) + + return common + + +def _parse_tile_layer(raw_layer: RawLayer) -> TileLayer: + """Parse the raw_layer to a TileLayer. + + Args: + raw_layer: RawLayer to be parsed to a TileLayer. + + Returns: + TileLayer: The TileLayer created from raw_layer + """ + tile_layer = TileLayer(**_parse_common(raw_layer).__dict__) + + if raw_layer.get("chunks") is not None: + tile_layer.chunks = [] + for chunk in raw_layer["chunks"]: + if raw_layer.get("encoding") is not None: + tile_layer.chunks.append( + _parse_chunk(chunk, raw_layer["encoding"], raw_layer["compression"]) + ) + else: + tile_layer.chunks.append(_parse_chunk(chunk)) + + if raw_layer.get("data") is not None: + if raw_layer.get("encoding") is not None: + tile_layer.data = _decode_tile_layer_data( + data=cast(str, raw_layer["data"]), + compression=raw_layer["compression"], + layer_width=raw_layer["width"], + ) + else: + tile_layer.data = _convert_raw_tile_layer_data( + raw_layer["data"], raw_layer["width"] # type: ignore + ) + + return tile_layer + + +def _parse_object_layer( + raw_layer: RawLayer, + parent_dir: Optional[Path] = None, +) -> ObjectLayer: + """Parse the raw_layer to an ObjectLayer. + + Args: + raw_layer: RawLayer to be parsed to an ObjectLayer. + + Returns: + ObjectLayer: The ObjectLayer created from raw_layer + """ + objects = [] + for object_ in raw_layer["objects"]: + objects.append(parse_object(object_, parent_dir)) + + return ObjectLayer( + tiled_objects=objects, + draw_order=raw_layer["draworder"], + **_parse_common(raw_layer).__dict__, + ) + + +def _parse_image_layer(raw_layer: RawLayer) -> ImageLayer: + """Parse the raw_layer to an ImageLayer. + + Args: + raw_layer: RawLayer to be parsed to an ImageLayer. + + Returns: + ImageLayer: The ImageLayer created from raw_layer + """ + image_layer = ImageLayer( + image=Path(raw_layer["image"]), **_parse_common(raw_layer).__dict__ + ) + + if raw_layer.get("transparentcolor") is not None: + image_layer.transparent_color = parse_color(raw_layer["transparentcolor"]) + + return image_layer + + +def _parse_group_layer( + raw_layer: RawLayer, parent_dir: Optional[Path] = None +) -> LayerGroup: + """Parse the raw_layer to a LayerGroup. + + Args: + raw_layer: RawLayer to be parsed to a LayerGroup. + + Returns: + LayerGroup: The LayerGroup created from raw_layer + """ + layers = [] + + for layer in raw_layer["layers"]: + layers.append(parse(layer, parent_dir=parent_dir)) + + return LayerGroup(layers=layers, **_parse_common(raw_layer).__dict__) + + +def parse( + raw_layer: RawLayer, + parent_dir: Optional[Path] = None, +) -> Layer: + """Parse a raw Layer into a pytiled_parser object. + + This function will determine the type of layer and parse accordingly. + + Args: + raw_layer: Raw layer to be parsed. + parent_dir: The parent directory that the map file is in. + + Returns: + Layer: A parsed Layer. + + Raises: + RuntimeError: For an invalid layer type being provided + """ + type_ = raw_layer["type"] + + if type_ == "objectgroup": + return _parse_object_layer(raw_layer, parent_dir) + elif type_ == "group": + return _parse_group_layer(raw_layer, parent_dir) + elif type_ == "imagelayer": + return _parse_image_layer(raw_layer) + elif type_ == "tilelayer": + return _parse_tile_layer(raw_layer) + + raise RuntimeError(f"An invalid layer type of {type_} was supplied") diff --git a/pytiled_parser/parsers/json/properties.py b/pytiled_parser/parsers/json/properties.py new file mode 100644 index 0000000..4e9896f --- /dev/null +++ b/pytiled_parser/parsers/json/properties.py @@ -0,0 +1,48 @@ +"""Property parsing for the JSON Map Format +""" + +from pathlib import Path +from typing import List, Union, cast + +from typing_extensions import TypedDict + +from pytiled_parser.properties import Properties, Property +from pytiled_parser.util import parse_color + +RawValue = Union[float, str, bool] + + +class RawProperty(TypedDict): + """The keys and their values that appear in a Tiled JSON Property Object. + + Tiled Docs: https://doc.mapeditor.org/en/stable/reference/json-map-format/#property + """ + + name: str + type: str + value: RawValue + + +def parse(raw_properties: List[RawProperty]) -> Properties: + """Parse a list of `RawProperty` objects into `Properties`. + + Args: + raw_properties: The list of `RawProperty` objects to parse. + + Returns: + Properties: The parsed `Property` objects. + """ + + final: Properties = {} + value: Property + + for raw_property in raw_properties: + if raw_property["type"] == "file": + value = Path(cast(str, raw_property["value"])) + elif raw_property["type"] == "color": + value = parse_color(cast(str, raw_property["value"])) + else: + value = raw_property["value"] + final[raw_property["name"]] = value + + return final diff --git a/pytiled_parser/parsers/json/tiled_map.py b/pytiled_parser/parsers/json/tiled_map.py new file mode 100644 index 0000000..304f577 --- /dev/null +++ b/pytiled_parser/parsers/json/tiled_map.py @@ -0,0 +1,153 @@ +import json +from pathlib import Path +from typing import List, Union, cast + +from typing_extensions import TypedDict + +from pytiled_parser.common_types import Size +from pytiled_parser.parsers.json.layer import RawLayer +from pytiled_parser.parsers.json.layer import parse as parse_layer +from pytiled_parser.parsers.json.properties import RawProperty +from pytiled_parser.parsers.json.properties import parse as parse_properties +from pytiled_parser.parsers.json.tileset import RawTileSet +from pytiled_parser.parsers.json.tileset import parse as parse_tileset +from pytiled_parser.tiled_map import TiledMap, TilesetDict +from pytiled_parser.util import parse_color + + +class RawTilesetMapping(TypedDict): + + firstgid: int + source: str + + +class RawTiledMap(TypedDict): + """The keys and their types that appear in a Tiled JSON Map Object. + + Tiled Docs: https://doc.mapeditor.org/en/stable/reference/json-map-format/#map + """ + + backgroundcolor: str + compressionlevel: int + height: int + hexsidelength: int + infinite: bool + layers: List[RawLayer] + nextlayerid: int + nextobjectid: int + orientation: str + properties: List[RawProperty] + renderorder: str + staggeraxis: str + staggerindex: str + tiledversion: str + tileheight: int + tilesets: List[RawTilesetMapping] + tilewidth: int + type: str + version: Union[str, float] + width: int + + +def parse(file: Path) -> TiledMap: + """Parse the raw Tiled map into a pytiled_parser type. + + Args: + file: Path to the map file. + + Returns: + TiledMap: A parsed TiledMap. + """ + with open(file) as map_file: + raw_tiled_map = json.load(map_file) + + parent_dir = file.parent + + raw_tilesets: List[Union[RawTileSet, RawTilesetMapping]] = raw_tiled_map["tilesets"] + tilesets: TilesetDict = {} + + for raw_tileset in raw_tilesets: + if raw_tileset.get("source") is not None: + # Is an external Tileset + tileset_path = Path(parent_dir / raw_tileset["source"]) + with open(tileset_path) as raw_tileset_file: + tilesets[raw_tileset["firstgid"]] = parse_tileset( + json.load(raw_tileset_file), + raw_tileset["firstgid"], + external_path=tileset_path.parent, + ) + else: + # Is an embedded Tileset + raw_tileset = cast(RawTileSet, raw_tileset) + tilesets[raw_tileset["firstgid"]] = parse_tileset( + raw_tileset, raw_tileset["firstgid"] + ) + + if isinstance(raw_tiled_map["version"], float): + version = str(raw_tiled_map["version"]) + else: + version = raw_tiled_map["version"] + + # `map` is a built-in function + map_ = TiledMap( + map_file=file, + infinite=raw_tiled_map["infinite"], + layers=[parse_layer(layer_, parent_dir) for layer_ in raw_tiled_map["layers"]], + map_size=Size(raw_tiled_map["width"], raw_tiled_map["height"]), + next_layer_id=raw_tiled_map["nextlayerid"], + next_object_id=raw_tiled_map["nextobjectid"], + orientation=raw_tiled_map["orientation"], + render_order=raw_tiled_map["renderorder"], + tiled_version=raw_tiled_map["tiledversion"], + tile_size=Size(raw_tiled_map["tilewidth"], raw_tiled_map["tileheight"]), + tilesets=tilesets, + version=version, + ) + + layers = [layer for layer in map_.layers if hasattr(layer, "tiled_objects")] + + for my_layer in layers: + for tiled_object in my_layer.tiled_objects: # type: ignore + if hasattr(tiled_object, "new_tileset"): + if tiled_object.new_tileset: + already_loaded = None + for val in map_.tilesets.values(): + if val.name == tiled_object.new_tileset["name"]: + already_loaded = val + break + + if not already_loaded: + highest_firstgid = max(map_.tilesets.keys()) + last_tileset_count = map_.tilesets[highest_firstgid].tile_count + new_firstgid = highest_firstgid + last_tileset_count + map_.tilesets[new_firstgid] = parse_tileset( + tiled_object.new_tileset, + new_firstgid, + tiled_object.new_tileset_path, + ) + tiled_object.gid = tiled_object.gid + (new_firstgid - 1) + + else: + tiled_object.gid = tiled_object.gid + ( + already_loaded.firstgid - 1 + ) + + tiled_object.new_tileset = None + tiled_object.new_tileset_path = None + + if raw_tiled_map.get("backgroundcolor") is not None: + map_.background_color = parse_color(raw_tiled_map["backgroundcolor"]) + + if raw_tiled_map.get("hexsidelength") is not None: + map_.hex_side_length = raw_tiled_map["hexsidelength"] + + if raw_tiled_map.get("properties") is not None: + map_.properties = parse_properties(raw_tiled_map["properties"]) + + if raw_tiled_map.get("staggeraxis") is not None: + map_.stagger_axis = raw_tiled_map["staggeraxis"] + + if raw_tiled_map.get("staggerindex") is not None: + map_.stagger_index = raw_tiled_map["staggerindex"] + + return map_ diff --git a/pytiled_parser/parsers/json/tiled_object.py b/pytiled_parser/parsers/json/tiled_object.py new file mode 100644 index 0000000..e537637 --- /dev/null +++ b/pytiled_parser/parsers/json/tiled_object.py @@ -0,0 +1,321 @@ +"""Object parsing for the JSON Map Format. +""" +import json +from pathlib import Path +from typing import Any, Callable, Dict, List, Optional + +from typing_extensions import TypedDict + +from pytiled_parser.common_types import OrderedPair, Size +from pytiled_parser.parsers.json.properties import RawProperty +from pytiled_parser.parsers.json.properties import parse as parse_properties +from pytiled_parser.tiled_object import ( + Ellipse, + Point, + Polygon, + Polyline, + Rectangle, + Text, + Tile, + TiledObject, +) +from pytiled_parser.util import parse_color + + +class RawText(TypedDict): + """The keys and their types that appear in a Tiled JSON Text Object. + + Tiled Doc: https://doc.mapeditor.org/en/stable/reference/json-map-format/#text-example + """ + + text: str + color: str + + fontfamily: str + pixelsize: float # this is `font_size` in Text + + bold: bool + italic: bool + strikeout: bool + underline: bool + kerning: bool + + halign: str + valign: str + wrap: bool + + +class RawObject(TypedDict): + """The keys and their types that appear in a Tiled JSON Object. + + Tiled Doc: https://doc.mapeditor.org/en/stable/reference/json-map-format/#object + """ + + id: int + gid: int + template: str + x: float + y: float + width: float + height: float + rotation: float + visible: bool + name: str + type: str + properties: List[RawProperty] + ellipse: bool + point: bool + polygon: List[Dict[str, float]] + polyline: List[Dict[str, float]] + text: RawText + + +def _parse_common(raw_object: RawObject) -> TiledObject: + """Create an Object containing all the attributes common to all types of objects. + + Args: + raw_object: Raw object to get common attributes from + + Returns: + Object: The attributes in common of all types of objects + """ + + common = TiledObject( + id=raw_object["id"], + coordinates=OrderedPair(raw_object["x"], raw_object["y"]), + visible=raw_object["visible"], + size=Size(raw_object["width"], raw_object["height"]), + rotation=raw_object["rotation"], + name=raw_object["name"], + type=raw_object["type"], + ) + + if raw_object.get("properties") is not None: + common.properties = parse_properties(raw_object["properties"]) + + return common + + +def _parse_ellipse(raw_object: RawObject) -> Ellipse: + """Parse the raw object into an Ellipse. + + Args: + raw_object: Raw object to be parsed to an Ellipse + + Returns: + Ellipse: The Ellipse object created from the raw object + """ + return Ellipse(**_parse_common(raw_object).__dict__) + + +def _parse_rectangle(raw_object: RawObject) -> Rectangle: + """Parse the raw object into a Rectangle. + + Args: + raw_object: Raw object to be parsed to a Rectangle + + Returns: + Rectangle: The Rectangle object created from the raw object + """ + return Rectangle(**_parse_common(raw_object).__dict__) + + +def _parse_point(raw_object: RawObject) -> Point: + """Parse the raw object into a Point. + + Args: + raw_object: Raw object to be parsed to a Point + + Returns: + Point: The Point object created from the raw object + """ + return Point(**_parse_common(raw_object).__dict__) + + +def _parse_polygon(raw_object: RawObject) -> Polygon: + """Parse the raw object into a Polygon. + + Args: + raw_object: Raw object to be parsed to a Polygon + + Returns: + Polygon: The Polygon object created from the raw object + """ + polygon = [] + for point in raw_object["polygon"]: + polygon.append(OrderedPair(point["x"], point["y"])) + + return Polygon(points=polygon, **_parse_common(raw_object).__dict__) + + +def _parse_polyline(raw_object: RawObject) -> Polyline: + """Parse the raw object into a Polyline. + + Args: + raw_object: Raw object to be parsed to a Polyline + + Returns: + Polyline: The Polyline object created from the raw object + """ + polyline = [] + for point in raw_object["polyline"]: + polyline.append(OrderedPair(point["x"], point["y"])) + + return Polyline(points=polyline, **_parse_common(raw_object).__dict__) + + +def _parse_tile( + raw_object: RawObject, + new_tileset: Optional[Dict[str, Any]] = None, + new_tileset_path: Optional[Path] = None, +) -> Tile: + """Parse the raw object into a Tile. + + Args: + raw_object: Raw object to be parsed to a Tile + + Returns: + Tile: The Tile object created from the raw object + """ + gid = raw_object["gid"] + + return Tile( + gid=gid, + new_tileset=new_tileset, + new_tileset_path=new_tileset_path, + **_parse_common(raw_object).__dict__ + ) + + +def _parse_text(raw_object: RawObject) -> Text: + """Parse the raw object into Text. + + Args: + raw_object: Raw object to be parsed to a Text + + Returns: + Text: The Text object created from the raw object + """ + # required attributes + raw_text: RawText = raw_object["text"] + text = raw_text["text"] + + # create base Text object + text_object = Text(text=text, **_parse_common(raw_object).__dict__) + + # optional attributes + if raw_text.get("color") is not None: + text_object.color = parse_color(raw_text["color"]) + + if raw_text.get("fontfamily") is not None: + text_object.font_family = raw_text["fontfamily"] + + if raw_text.get("pixelsize") is not None: + text_object.font_size = raw_text["pixelsize"] + + if raw_text.get("bold") is not None: + text_object.bold = raw_text["bold"] + + if raw_text.get("italic") is not None: + text_object.italic = raw_text["italic"] + + if raw_text.get("kerning") is not None: + text_object.kerning = raw_text["kerning"] + + if raw_text.get("strikeout") is not None: + text_object.strike_out = raw_text["strikeout"] + + if raw_text.get("underline") is not None: + text_object.underline = raw_text["underline"] + + if raw_text.get("halign") is not None: + text_object.horizontal_align = raw_text["halign"] + + if raw_text.get("valign") is not None: + text_object.vertical_align = raw_text["valign"] + + if raw_text.get("wrap") is not None: + text_object.wrap = raw_text["wrap"] + + return text_object + + +def _get_parser(raw_object: RawObject) -> Callable[[RawObject], TiledObject]: + """Get the parser function for a given raw object. + + Only used internally by the JSON parser. + + Args: + raw_object: Raw object that is analyzed to determine the parser function. + + Returns: + Callable[[RawObject], Object]: The parser function. + """ + if raw_object.get("ellipse"): + return _parse_ellipse + + if raw_object.get("point"): + return _parse_point + + if raw_object.get("gid"): + # Only tile objects have the `gid` key + return _parse_tile + + if raw_object.get("polygon"): + return _parse_polygon + + if raw_object.get("polyline"): + return _parse_polyline + + if raw_object.get("text"): + return _parse_text + + # If it's none of the above, rectangle is the only one left. + # Rectangle is the only object which has no special properties to signify that. + return _parse_rectangle + + +def parse( + raw_object: RawObject, + parent_dir: Optional[Path] = None, +) -> TiledObject: + """Parse the raw object into a pytiled_parser version + + Args: + raw_object: Raw object that is to be cast. + parent_dir: The parent directory that the map file is in. + + Returns: + Object: A parsed Object. + + Raises: + RuntimeError: When a parameter that is conditionally required was not sent. + """ + new_tileset = None + new_tileset_path = None + + if raw_object.get("template"): + if not parent_dir: + raise RuntimeError( + "A parent directory must be specified when using object templates." + ) + template_path = Path(parent_dir / raw_object["template"]) + with open(template_path) as raw_template_file: + template = json.load(raw_template_file) + if "tileset" in template: + tileset_path = Path( + template_path.parent / template["tileset"]["source"] + ) + with open(tileset_path) as raw_tileset_file: + new_tileset = json.load(raw_tileset_file) + new_tileset_path = tileset_path.parent + + loaded_template = template["object"] + for key in loaded_template: + if key != "id": + raw_object[key] = loaded_template[key] # type: ignore + + if raw_object.get("gid"): + return _parse_tile(raw_object, new_tileset, new_tileset_path) + + return _get_parser(raw_object)(raw_object) diff --git a/pytiled_parser/parsers/json/tileset.py b/pytiled_parser/parsers/json/tileset.py new file mode 100644 index 0000000..3206bac --- /dev/null +++ b/pytiled_parser/parsers/json/tileset.py @@ -0,0 +1,272 @@ +from pathlib import Path +from typing import List, Optional, Union + +from typing_extensions import TypedDict + +from pytiled_parser.common_types import OrderedPair +from pytiled_parser.parsers.json.layer import RawLayer +from pytiled_parser.parsers.json.layer import parse as parse_layer +from pytiled_parser.parsers.json.properties import RawProperty +from pytiled_parser.parsers.json.properties import parse as parse_properties +from pytiled_parser.parsers.json.wang_set import RawWangSet +from pytiled_parser.parsers.json.wang_set import parse as parse_wangset +from pytiled_parser.tileset import Frame, Grid, Tile, Tileset, Transformations +from pytiled_parser.util import parse_color + + +class RawFrame(TypedDict): + """The keys and their types that appear in a Frame JSON Object.""" + + duration: int + tileid: int + + +class RawTileOffset(TypedDict): + """The keys and their types that appear in a TileOffset JSON Object.""" + + x: int + y: int + + +class RawTransformations(TypedDict): + """The keys and their types that appear in a Transformations JSON Object.""" + + hflip: bool + vflip: bool + rotate: bool + preferuntransformed: bool + + +class RawTile(TypedDict): + """The keys and their types that appear in a Tile JSON Object.""" + + animation: List[RawFrame] + id: int + image: str + imageheight: int + imagewidth: int + opacity: float + properties: List[RawProperty] + objectgroup: RawLayer + type: str + + +class RawGrid(TypedDict): + """The keys and their types that appear in a Grid JSON Object.""" + + height: int + width: int + orientation: str + + +class RawTileSet(TypedDict): + """The keys and their types that appear in a TileSet JSON Object.""" + + backgroundcolor: str + columns: int + firstgid: int + grid: RawGrid + image: str + imageheight: int + imagewidth: int + margin: int + name: str + properties: List[RawProperty] + source: str + spacing: int + tilecount: int + tiledversion: str + tileheight: int + tileoffset: RawTileOffset + tiles: List[RawTile] + tilewidth: int + transparentcolor: str + transformations: RawTransformations + version: Union[str, float] + wangsets: List[RawWangSet] + + +def _parse_frame(raw_frame: RawFrame) -> Frame: + """Parse the raw_frame to a Frame. + + Args: + raw_frame: RawFrame to be parsed to a Frame + + Returns: + Frame: The Frame created from the raw_frame + """ + + return Frame(duration=raw_frame["duration"], tile_id=raw_frame["tileid"]) + + +def _parse_tile_offset(raw_tile_offset: RawTileOffset) -> OrderedPair: + """Parse the raw_tile_offset to an OrderedPair. + + Args: + raw_tile_offset: RawTileOffset to be parsed to an OrderedPair + + Returns: + OrderedPair: The OrderedPair created from the raw_tile_offset + """ + + return OrderedPair(raw_tile_offset["x"], raw_tile_offset["y"]) + + +def _parse_transformations(raw_transformations: RawTransformations) -> Transformations: + """Parse the raw_transformations to a Transformations object. + + Args: + raw_transformations: RawTransformations to be parsed to a Transformations + + Returns: + Transformations: The Transformations created from the raw_transformations + """ + + return Transformations( + hflip=raw_transformations["hflip"], + vflip=raw_transformations["vflip"], + rotate=raw_transformations["rotate"], + prefer_untransformed=raw_transformations["preferuntransformed"], + ) + + +def _parse_grid(raw_grid: RawGrid) -> Grid: + """Parse the raw_grid to a Grid object. + + Args: + raw_grid: RawGrid to be parsed to a Grid + + Returns: + Grid: The Grid created from the raw_grid + """ + + return Grid( + orientation=raw_grid["orientation"], + width=raw_grid["width"], + height=raw_grid["height"], + ) + + +def _parse_tile(raw_tile: RawTile, external_path: Optional[Path] = None) -> Tile: + """Parse the raw_tile to a Tile object. + + Args: + raw_tile: RawTile to be parsed to a Tile + + Returns: + Tile: The Tile created from the raw_tile + """ + + id_ = raw_tile["id"] + tile = Tile(id=id_) + + if raw_tile.get("animation") is not None: + tile.animation = [] + for frame in raw_tile["animation"]: + tile.animation.append(_parse_frame(frame)) + + if raw_tile.get("objectgroup") is not None: + tile.objects = parse_layer(raw_tile["objectgroup"]) + + if raw_tile.get("properties") is not None: + tile.properties = parse_properties(raw_tile["properties"]) + + if raw_tile.get("image") is not None: + if external_path: + tile.image = Path(external_path / raw_tile["image"]).absolute().resolve() + else: + tile.image = Path(raw_tile["image"]) + + if raw_tile.get("imagewidth") is not None: + tile.image_width = raw_tile["imagewidth"] + + if raw_tile.get("imageheight") is not None: + tile.image_height = raw_tile["imageheight"] + + if raw_tile.get("type") is not None: + tile.type = raw_tile["type"] + + return tile + + +def parse( + raw_tileset: RawTileSet, + firstgid: int, + external_path: Optional[Path] = None, +) -> Tileset: + """Parse the raw tileset into a pytiled_parser type + + Args: + raw_tileset: Raw Tileset to be parsed. + firstgid: GID corresponding the first tile in the set. + external_path: The path to the tileset if it is not an embedded one. + + Returns: + TileSet: a properly typed TileSet. + """ + + tileset = Tileset( + name=raw_tileset["name"], + tile_count=raw_tileset["tilecount"], + tile_width=raw_tileset["tilewidth"], + tile_height=raw_tileset["tileheight"], + columns=raw_tileset["columns"], + spacing=raw_tileset["spacing"], + margin=raw_tileset["margin"], + firstgid=firstgid, + ) + + if raw_tileset.get("version") is not None: + if isinstance(raw_tileset["version"], float): + tileset.version = str(raw_tileset["version"]) + else: + tileset.version = raw_tileset["version"] + + if raw_tileset.get("tiledversion") is not None: + tileset.tiled_version = raw_tileset["tiledversion"] + + if raw_tileset.get("image") is not None: + if external_path: + tileset.image = ( + Path(external_path / raw_tileset["image"]).absolute().resolve() + ) + else: + tileset.image = Path(raw_tileset["image"]) + + if raw_tileset.get("imagewidth") is not None: + tileset.image_width = raw_tileset["imagewidth"] + + if raw_tileset.get("imageheight") is not None: + tileset.image_height = raw_tileset["imageheight"] + + if raw_tileset.get("backgroundcolor") is not None: + tileset.background_color = parse_color(raw_tileset["backgroundcolor"]) + + if raw_tileset.get("tileoffset") is not None: + tileset.tile_offset = _parse_tile_offset(raw_tileset["tileoffset"]) + + if raw_tileset.get("transparentcolor") is not None: + tileset.transparent_color = parse_color(raw_tileset["transparentcolor"]) + + if raw_tileset.get("grid") is not None: + tileset.grid = _parse_grid(raw_tileset["grid"]) + + if raw_tileset.get("properties") is not None: + tileset.properties = parse_properties(raw_tileset["properties"]) + + if raw_tileset.get("tiles") is not None: + tiles = {} + for raw_tile in raw_tileset["tiles"]: + tiles[raw_tile["id"]] = _parse_tile(raw_tile, external_path=external_path) + tileset.tiles = tiles + + if raw_tileset.get("wangsets") is not None: + wangsets = [] + for raw_wangset in raw_tileset["wangsets"]: + wangsets.append(parse_wangset(raw_wangset)) + tileset.wang_sets = wangsets + + if raw_tileset.get("transformations") is not None: + tileset.transformations = _parse_transformations(raw_tileset["transformations"]) + + return tileset diff --git a/pytiled_parser/parsers/json/wang_set.py b/pytiled_parser/parsers/json/wang_set.py new file mode 100644 index 0000000..ea68905 --- /dev/null +++ b/pytiled_parser/parsers/json/wang_set.py @@ -0,0 +1,104 @@ +from typing import List + +from typing_extensions import TypedDict + +from pytiled_parser.parsers.json.properties import RawProperty +from pytiled_parser.parsers.json.properties import parse as parse_properties +from pytiled_parser.util import parse_color +from pytiled_parser.wang_set import WangColor, WangSet, WangTile + + +class RawWangTile(TypedDict): + """The keys and their types that appear in a Wang Tile JSON Object.""" + + tileid: int + # Tiled stores these IDs as a list represented like so: + # [top, top_right, right, bottom_right, bottom, bottom_left, left, top_left] + wangid: List[int] + + +class RawWangColor(TypedDict): + """The keys and their types that appear in a Wang Color JSON Object.""" + + color: str + name: str + probability: float + tile: int + properties: List[RawProperty] + + +class RawWangSet(TypedDict): + """The keys and their types that appear in a Wang Set JSON Object.""" + + colors: List[RawWangColor] + name: str + properties: List[RawProperty] + tile: int + type: str + wangtiles: List[RawWangTile] + + +def _parse_wang_tile(raw_wang_tile: RawWangTile) -> WangTile: + """Parse the raw wang tile into a pytiled_parser type + + Args: + raw_wang_tile: RawWangTile to be parsed. + + Returns: + WangTile: A properly typed WangTile. + """ + return WangTile(tile_id=raw_wang_tile["tileid"], wang_id=raw_wang_tile["wangid"]) + + +def _parse_wang_color(raw_wang_color: RawWangColor) -> WangColor: + """Parse the raw wang color into a pytiled_parser type + + Args: + raw_wang_color: RawWangColor to be parsed. + + Returns: + WangColor: A properly typed WangColor. + """ + wang_color = WangColor( + name=raw_wang_color["name"], + color=parse_color(raw_wang_color["color"]), + tile=raw_wang_color["tile"], + probability=raw_wang_color["probability"], + ) + + if raw_wang_color.get("properties") is not None: + wang_color.properties = parse_properties(raw_wang_color["properties"]) + + return wang_color + + +def parse(raw_wangset: RawWangSet) -> WangSet: + """Parse the raw wangset into a pytiled_parser type + + Args: + raw_wangset: Raw Wangset to be parsed. + + Returns: + WangSet: A properly typed WangSet. + """ + + colors = [] + for raw_wang_color in raw_wangset["colors"]: + colors.append(_parse_wang_color(raw_wang_color)) + + tiles = {} + for raw_wang_tile in raw_wangset["wangtiles"]: + tiles[raw_wang_tile["tileid"]] = _parse_wang_tile(raw_wang_tile) + + wangset = WangSet( + name=raw_wangset["name"], + tile=raw_wangset["tile"], + wang_type=raw_wangset["type"], + wang_colors=colors, + wang_tiles=tiles, + ) + + if raw_wangset.get("properties") is not None: + wangset.properties = parse_properties(raw_wangset["properties"]) + + return wangset diff --git a/pytiled_parser/properties.py b/pytiled_parser/properties.py index e9b87b5..f8bc0ac 100644 --- a/pytiled_parser/properties.py +++ b/pytiled_parser/properties.py @@ -1,55 +1,18 @@ """Properties Module -This module casts raw properties from Tiled maps into a dictionary of -properly typed Properties. +This module defines types for Property objects. +For more about properties in Tiled maps see the below link: +https://doc.mapeditor.org/en/stable/manual/custom-properties/ + +The types defined in this module get added to other objects +such as Layers, Maps, Objects, etc """ from pathlib import Path -from typing import Dict, List, Union -from typing import cast as type_cast - -from typing_extensions import TypedDict +from typing import Dict, Union from .common_types import Color -from .util import parse_color Property = Union[float, Path, str, bool, Color] - Properties = Dict[str, Property] - - -RawValue = Union[float, str, bool] - - -class RawProperty(TypedDict): - """A dictionary of raw properties.""" - - name: str - type: str - value: RawValue - - -def cast(raw_properties: List[RawProperty]) -> Properties: - """Cast a list of `RawProperty`s into `Properties` - - Args: - raw_properties: The list of `RawProperty`s to cast. - - Returns: - Properties: The casted `Properties`. - """ - - final: Properties = {} - value: Property - - for property_ in raw_properties: - if property_["type"] == "file": - value = Path(type_cast(str, property_["value"])) - elif property_["type"] == "color": - value = parse_color(type_cast(str, property_["value"])) - else: - value = property_["value"] - final[property_["name"]] = value - - return final diff --git a/pytiled_parser/tiled_map.py b/pytiled_parser/tiled_map.py index 1f56885..9c1c1ba 100644 --- a/pytiled_parser/tiled_map.py +++ b/pytiled_parser/tiled_map.py @@ -1,19 +1,12 @@ -# pylint: disable=too-few-public-methods - -import json from pathlib import Path -from typing import Dict, List, Optional, Union -from typing import cast as typing_cast +from typing import Dict, List, Optional import attr -from typing_extensions import TypedDict -from . import layer, properties, tileset -from .common_types import Color, Size -from .layer import Layer, RawLayer -from .properties import Properties, RawProperty -from .tileset import RawTileSet, Tileset -from .util import parse_color +from pytiled_parser.common_types import Color, Size +from pytiled_parser.layer import Layer +from pytiled_parser.properties import Properties +from pytiled_parser.tileset import Tileset TilesetDict = Dict[int, Tileset] @@ -68,146 +61,3 @@ class TiledMap: hex_side_length: Optional[int] = None stagger_axis: Optional[str] = None stagger_index: Optional[str] = None - - -class _RawTilesetMapping(TypedDict): - """ The way that tilesets are stored in the Tiled JSON formatted map.""" - - firstgid: int - source: str - - -class _RawTiledMap(TypedDict): - """The keys and their types that appear in a Tiled JSON Map. - - Keys: - compressionlevel: not documented - https://github.com/bjorn/tiled/issues/2815 - """ - - backgroundcolor: str - compressionlevel: int - height: int - hexsidelength: int - infinite: bool - layers: List[RawLayer] - nextlayerid: int - nextobjectid: int - orientation: str - properties: List[RawProperty] - renderorder: str - staggeraxis: str - staggerindex: str - tiledversion: str - tileheight: int - tilesets: List[_RawTilesetMapping] - tilewidth: int - type: str - version: Union[str, float] - width: int - - -def parse_map(file: Path) -> TiledMap: - """Parse the raw Tiled map into a pytiled_parser type - - Args: - file: Path to the map's JSON file - - Returns: - TileSet: a properly typed TileSet. - """ - - with open(file) as map_file: - raw_tiled_map = json.load(map_file) - - parent_dir = file.parent - - raw_tilesets: List[Union[RawTileSet, _RawTilesetMapping]] = raw_tiled_map[ - "tilesets" - ] - tilesets: TilesetDict = {} - - for raw_tileset in raw_tilesets: - if raw_tileset.get("source") is not None: - # Is an external Tileset - tileset_path = Path(parent_dir / raw_tileset["source"]) - with open(tileset_path) as raw_tileset_file: - tilesets[raw_tileset["firstgid"]] = tileset.cast( - json.load(raw_tileset_file), - raw_tileset["firstgid"], - external_path=tileset_path.parent, - ) - else: - # Is an embedded Tileset - raw_tileset = typing_cast(RawTileSet, raw_tileset) - tilesets[raw_tileset["firstgid"]] = tileset.cast( - raw_tileset, raw_tileset["firstgid"] - ) - - if isinstance(raw_tiled_map["version"], float): - version = str(raw_tiled_map["version"]) - else: - version = raw_tiled_map["version"] - - # `map` is a built-in function - map_ = TiledMap( - map_file=file, - infinite=raw_tiled_map["infinite"], - layers=[layer.cast(layer_, parent_dir) for layer_ in raw_tiled_map["layers"]], - map_size=Size(raw_tiled_map["width"], raw_tiled_map["height"]), - next_layer_id=raw_tiled_map["nextlayerid"], - next_object_id=raw_tiled_map["nextobjectid"], - orientation=raw_tiled_map["orientation"], - render_order=raw_tiled_map["renderorder"], - tiled_version=raw_tiled_map["tiledversion"], - tile_size=Size(raw_tiled_map["tilewidth"], raw_tiled_map["tileheight"]), - tilesets=tilesets, - version=version, - ) - - layers = [layer for layer in map_.layers if hasattr(layer, "tiled_objects")] - - for my_layer in layers: - for tiled_object in my_layer.tiled_objects: # type: ignore - if hasattr(tiled_object, "new_tileset"): - if tiled_object.new_tileset: - already_loaded = None - for val in map_.tilesets.values(): - if val.name == tiled_object.new_tileset["name"]: - already_loaded = val - break - - if not already_loaded: - highest_firstgid = max(map_.tilesets.keys()) - last_tileset_count = map_.tilesets[highest_firstgid].tile_count - new_firstgid = highest_firstgid + last_tileset_count - map_.tilesets[new_firstgid] = tileset.cast( - tiled_object.new_tileset, - new_firstgid, - tiled_object.new_tileset_path, - ) - tiled_object.gid = tiled_object.gid + (new_firstgid - 1) - - else: - tiled_object.gid = tiled_object.gid + ( - already_loaded.firstgid - 1 - ) - - tiled_object.new_tileset = None - tiled_object.new_tileset_path = None - - if raw_tiled_map.get("backgroundcolor") is not None: - map_.background_color = parse_color(raw_tiled_map["backgroundcolor"]) - - if raw_tiled_map.get("hexsidelength") is not None: - map_.hex_side_length = raw_tiled_map["hexsidelength"] - - if raw_tiled_map.get("properties") is not None: - map_.properties = properties.cast(raw_tiled_map["properties"]) - - if raw_tiled_map.get("staggeraxis") is not None: - map_.stagger_axis = raw_tiled_map["staggeraxis"] - - if raw_tiled_map.get("staggerindex") is not None: - map_.stagger_index = raw_tiled_map["staggerindex"] - - return map_ diff --git a/pytiled_parser/tiled_object.py b/pytiled_parser/tiled_object.py index 3d167d0..17de14e 100644 --- a/pytiled_parser/tiled_object.py +++ b/pytiled_parser/tiled_object.py @@ -1,14 +1,11 @@ # pylint: disable=too-few-public-methods -import json from pathlib import Path -from typing import Any, Callable, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional import attr -from typing_extensions import TypedDict from . import properties as properties_ from .common_types import Color, OrderedPair, Size -from .util import parse_color @attr.s(auto_attribs=True, kw_only=True) @@ -150,300 +147,3 @@ class Tile(TiledObject): gid: int new_tileset: Optional[Dict[str, Any]] = None new_tileset_path: Optional[Path] = None - - -class RawTextDict(TypedDict): - """ The keys and their types that appear in a Text JSON Object.""" - - text: str - color: str - - fontfamily: str - pixelsize: float # this is `font_size` in Text - - bold: bool - italic: bool - strikeout: bool - underline: bool - kerning: bool - - halign: str - valign: str - wrap: bool - - -class RawTiledObject(TypedDict): - """ The keys and their types that appear in a Tiled JSON Object.""" - - id: int - gid: int - template: str - x: float - y: float - width: float - height: float - rotation: float - visible: bool - name: str - type: str - properties: List[properties_.RawProperty] - ellipse: bool - point: bool - polygon: List[Dict[str, float]] - polyline: List[Dict[str, float]] - text: Dict[str, Union[float, str]] - - -RawTiledObjects = List[RawTiledObject] - - -def _get_common_attributes(raw_tiled_object: RawTiledObject) -> TiledObject: - """Create a TiledObject containing all the attributes common to all tiled objects - - Args: - raw_tiled_object: Raw Tiled object get common attributes from - - Returns: - TiledObject: The attributes in common of all Tiled Objects - """ - - common_attributes = TiledObject( - id=raw_tiled_object["id"], - coordinates=OrderedPair(raw_tiled_object["x"], raw_tiled_object["y"]), - visible=raw_tiled_object["visible"], - size=Size(raw_tiled_object["width"], raw_tiled_object["height"]), - rotation=raw_tiled_object["rotation"], - name=raw_tiled_object["name"], - type=raw_tiled_object["type"], - ) - - if raw_tiled_object.get("properties") is not None: - common_attributes.properties = properties_.cast(raw_tiled_object["properties"]) - - return common_attributes - - -def _cast_ellipse(raw_tiled_object: RawTiledObject) -> Ellipse: - """Cast the raw_tiled_object to an Ellipse object. - - Args: - raw_tiled_object: Raw Tiled object to be casted to an Ellipse - - Returns: - Ellipse: The Ellipse object created from the raw_tiled_object - """ - return Ellipse(**_get_common_attributes(raw_tiled_object).__dict__) - - -def _cast_rectangle(raw_tiled_object: RawTiledObject) -> Rectangle: - """Cast the raw_tiled_object to a Rectangle object. - - Args: - raw_tiled_object: Raw Tiled object to be casted to a Rectangle - - Returns: - Rectangle: The Rectangle object created from the raw_tiled_object - """ - return Rectangle(**_get_common_attributes(raw_tiled_object).__dict__) - - -def _cast_point(raw_tiled_object: RawTiledObject) -> Point: - """Cast the raw_tiled_object to a Point object. - - Args: - raw_tiled_object: Raw Tiled object to be casted to a Point - - Returns: - Point: The Point object created from the raw_tiled_object - """ - return Point(**_get_common_attributes(raw_tiled_object).__dict__) - - -def _cast_tile( - raw_tiled_object: RawTiledObject, - new_tileset: Optional[Dict[str, Any]] = None, - new_tileset_path: Optional[Path] = None, -) -> Tile: - """Cast the raw_tiled_object to a Tile object. - - Args: - raw_tiled_object: Raw Tiled object to be casted to a Tile - - Returns: - Tile: The Tile object created from the raw_tiled_object - """ - gid = raw_tiled_object["gid"] - - return Tile( - gid=gid, - new_tileset=new_tileset, - new_tileset_path=new_tileset_path, - **_get_common_attributes(raw_tiled_object).__dict__ - ) - - -def _cast_polygon(raw_tiled_object: RawTiledObject) -> Polygon: - """Cast the raw_tiled_object to a Polygon object. - - Args: - raw_tiled_object: Raw Tiled object to be casted to a Polygon - - Returns: - Polygon: The Polygon object created from the raw_tiled_object - """ - polygon = [] - for point in raw_tiled_object["polygon"]: - polygon.append(OrderedPair(point["x"], point["y"])) - - return Polygon(points=polygon, **_get_common_attributes(raw_tiled_object).__dict__) - - -def _cast_polyline(raw_tiled_object: RawTiledObject) -> Polyline: - """Cast the raw_tiled_object to a Polyline object. - - Args: - raw_tiled_object: Raw Tiled Object to be casted to a Polyline - - Returns: - Polyline: The Polyline object created from the raw_tiled_object - """ - polyline = [] - for point in raw_tiled_object["polyline"]: - polyline.append(OrderedPair(point["x"], point["y"])) - - return Polyline( - points=polyline, **_get_common_attributes(raw_tiled_object).__dict__ - ) - - -def _cast_text(raw_tiled_object: RawTiledObject) -> Text: - """Cast the raw_tiled_object to a Text object. - - Args: - raw_tiled_object: Raw Tiled object to be casted to a Text object - - Returns: - Text: The Text object created from the raw_tiled_object - """ - # required attributes - raw_text_dict: RawTextDict = raw_tiled_object["text"] - text = raw_text_dict["text"] - - # create base Text object - text_object = Text(text=text, **_get_common_attributes(raw_tiled_object).__dict__) - - # optional attributes - if raw_text_dict.get("color") is not None: - text_object.color = parse_color(raw_text_dict["color"]) - - if raw_text_dict.get("fontfamily") is not None: - text_object.font_family = raw_text_dict["fontfamily"] - - if raw_text_dict.get("pixelsize") is not None: - text_object.font_size = raw_text_dict["pixelsize"] - - if raw_text_dict.get("bold") is not None: - text_object.bold = raw_text_dict["bold"] - - if raw_text_dict.get("italic") is not None: - text_object.italic = raw_text_dict["italic"] - - if raw_text_dict.get("kerning") is not None: - text_object.kerning = raw_text_dict["kerning"] - - if raw_text_dict.get("strikeout") is not None: - text_object.strike_out = raw_text_dict["strikeout"] - - if raw_text_dict.get("underline") is not None: - text_object.underline = raw_text_dict["underline"] - - if raw_text_dict.get("halign") is not None: - text_object.horizontal_align = raw_text_dict["halign"] - - if raw_text_dict.get("valign") is not None: - text_object.vertical_align = raw_text_dict["valign"] - - if raw_text_dict.get("wrap") is not None: - text_object.wrap = raw_text_dict["wrap"] - - return text_object - - -def _get_caster( - raw_tiled_object: RawTiledObject, -) -> Callable[[RawTiledObject], TiledObject]: - """Get the caster function for the raw tiled object. - - Args: - raw_tiled_object: Raw Tiled object that is analysed to determine which caster - to return. - - Returns: - Callable[[RawTiledObject], TiledObject]: The caster function. - """ - if raw_tiled_object.get("ellipse"): - return _cast_ellipse - - if raw_tiled_object.get("point"): - return _cast_point - - if raw_tiled_object.get("gid"): - # Only Tile objects have the `gid` key (I think) - return _cast_tile - - if raw_tiled_object.get("polygon"): - return _cast_polygon - - if raw_tiled_object.get("polyline"): - return _cast_polyline - - if raw_tiled_object.get("text"): - return _cast_text - - return _cast_rectangle - - -def cast( - raw_tiled_object: RawTiledObject, - parent_dir: Optional[Path] = None, -) -> TiledObject: - """Cast the raw tiled object into a pytiled_parser type - - Args: - raw_tiled_object: Raw Tiled object that is to be cast. - parent_dir: The parent directory that the map file is in. - - Returns: - TiledObject: a properly typed Tiled object. - - Raises: - RuntimeError: When a required parameter was not sent based on a condition. - """ - new_tileset = None - new_tileset_path = None - - if raw_tiled_object.get("template"): - if not parent_dir: - raise RuntimeError( - "A parent directory must be specified when using object templates" - ) - template_path = Path(parent_dir / raw_tiled_object["template"]) - with open(template_path) as raw_template_file: - template = json.load(raw_template_file) - if "tileset" in template: - tileset_path = Path( - template_path.parent / template["tileset"]["source"] - ) - with open(tileset_path) as raw_tileset_file: - new_tileset = json.load(raw_tileset_file) - new_tileset_path = tileset_path.parent - - loaded_template = template["object"] - for key in loaded_template: - if key != "id": - raw_tiled_object[key] = loaded_template[key] # type: ignore - - if raw_tiled_object.get("gid"): - return _cast_tile(raw_tiled_object, new_tileset, new_tileset_path) - - return _get_caster(raw_tiled_object)(raw_tiled_object) diff --git a/pytiled_parser/tileset.py b/pytiled_parser/tileset.py index 25bd190..de48013 100644 --- a/pytiled_parser/tileset.py +++ b/pytiled_parser/tileset.py @@ -1,16 +1,13 @@ # pylint: disable=too-few-public-methods from pathlib import Path -from typing import Dict, List, NamedTuple, Optional, Union +from typing import Dict, List, NamedTuple, Optional import attr -from typing_extensions import TypedDict from . import layer from . import properties as properties_ from .common_types import Color, OrderedPair -from .util import parse_color -from .wang_set import RawWangSet, WangSet -from .wang_set import cast as cast_wangset +from .wang_set import WangSet class Grid(NamedTuple): @@ -153,261 +150,3 @@ class Tileset: properties: Optional[properties_.Properties] = None tiles: Optional[Dict[int, Tile]] = None wang_sets: Optional[List[WangSet]] = None - - -class RawFrame(TypedDict): - """ The keys and their types that appear in a Frame JSON Object.""" - - duration: int - tileid: int - - -class RawTileOffset(TypedDict): - """ The keys and their types that appear in a TileOffset JSON Object.""" - - x: int - y: int - - -class RawTransformations(TypedDict): - """ The keys and their types that appear in a Transformations JSON Object.""" - - hflip: bool - vflip: bool - rotate: bool - preferuntransformed: bool - - -class RawTile(TypedDict): - """ The keys and their types that appear in a Tile JSON Object.""" - - animation: List[RawFrame] - id: int - image: str - imageheight: int - imagewidth: int - opacity: float - properties: List[properties_.RawProperty] - objectgroup: layer.RawLayer - type: str - - -class RawGrid(TypedDict): - """ The keys and their types that appear in a Grid JSON Object.""" - - height: int - width: int - orientation: str - - -class RawTileSet(TypedDict): - """ The keys and their types that appear in a TileSet JSON Object.""" - - backgroundcolor: str - columns: int - firstgid: int - grid: RawGrid - image: str - imageheight: int - imagewidth: int - margin: int - name: str - properties: List[properties_.RawProperty] - source: str - spacing: int - tilecount: int - tiledversion: str - tileheight: int - tileoffset: RawTileOffset - tiles: List[RawTile] - tilewidth: int - transparentcolor: str - transformations: RawTransformations - version: Union[str, float] - wangsets: List[RawWangSet] - - -def _cast_frame(raw_frame: RawFrame) -> Frame: - """Cast the raw_frame to a Frame. - - Args: - raw_frame: RawFrame to be casted to a Frame - - Returns: - Frame: The Frame created from the raw_frame - """ - - return Frame(duration=raw_frame["duration"], tile_id=raw_frame["tileid"]) - - -def _cast_tile_offset(raw_tile_offset: RawTileOffset) -> OrderedPair: - """Cast the raw_tile_offset to an OrderedPair. - - Args: - raw_tile_offset: RawTileOffset to be casted to an OrderedPair - - Returns: - OrderedPair: The OrderedPair created from the raw_tile_offset - """ - - return OrderedPair(raw_tile_offset["x"], raw_tile_offset["y"]) - - -def _cast_tile(raw_tile: RawTile, external_path: Optional[Path] = None) -> Tile: - """Cast the raw_tile to a Tile object. - - Args: - raw_tile: RawTile to be casted to a Tile - - Returns: - Tile: The Tile created from the raw_tile - """ - - id_ = raw_tile["id"] - tile = Tile(id=id_) - - if raw_tile.get("animation") is not None: - tile.animation = [] - for frame in raw_tile["animation"]: - tile.animation.append(_cast_frame(frame)) - - if raw_tile.get("objectgroup") is not None: - tile.objects = layer.cast(raw_tile["objectgroup"]) - - if raw_tile.get("properties") is not None: - tile.properties = properties_.cast(raw_tile["properties"]) - - if raw_tile.get("image") is not None: - if external_path: - tile.image = Path(external_path / raw_tile["image"]).absolute().resolve() - else: - tile.image = Path(raw_tile["image"]) - - if raw_tile.get("imagewidth") is not None: - tile.image_width = raw_tile["imagewidth"] - - if raw_tile.get("imageheight") is not None: - tile.image_height = raw_tile["imageheight"] - - if raw_tile.get("type") is not None: - tile.type = raw_tile["type"] - - return tile - - -def _cast_transformations(raw_transformations: RawTransformations) -> Transformations: - """Cast the raw_transformations to a Transformations object. - - Args: - raw_transformations: RawTransformations to be casted to a Transformations - - Returns: - Transformations: The Transformations created from the raw_transformations - """ - - return Transformations( - hflip=raw_transformations["hflip"], - vflip=raw_transformations["vflip"], - rotate=raw_transformations["rotate"], - prefer_untransformed=raw_transformations["preferuntransformed"], - ) - - -def _cast_grid(raw_grid: RawGrid) -> Grid: - """Cast the raw_grid to a Grid object. - - Args: - raw_grid: RawGrid to be casted to a Grid - - Returns: - Grid: The Grid created from the raw_grid - """ - - return Grid( - orientation=raw_grid["orientation"], - width=raw_grid["width"], - height=raw_grid["height"], - ) - - -def cast( - raw_tileset: RawTileSet, - firstgid: int, - external_path: Optional[Path] = None, -) -> Tileset: - """Cast the raw tileset into a pytiled_parser type - - Args: - raw_tileset: Raw Tileset to be cast. - firstgid: GID corresponding the first tile in the set. - external_path: The path to the tileset if it is not an embedded one. - - Returns: - TileSet: a properly typed TileSet. - """ - - tileset = Tileset( - name=raw_tileset["name"], - tile_count=raw_tileset["tilecount"], - tile_width=raw_tileset["tilewidth"], - tile_height=raw_tileset["tileheight"], - columns=raw_tileset["columns"], - spacing=raw_tileset["spacing"], - margin=raw_tileset["margin"], - firstgid=firstgid, - ) - - if raw_tileset.get("version") is not None: - if isinstance(raw_tileset["version"], float): - tileset.version = str(raw_tileset["version"]) - else: - tileset.version = raw_tileset["version"] - - if raw_tileset.get("tiledversion") is not None: - tileset.tiled_version = raw_tileset["tiledversion"] - - if raw_tileset.get("image") is not None: - if external_path: - tileset.image = ( - Path(external_path / raw_tileset["image"]).absolute().resolve() - ) - else: - tileset.image = Path(raw_tileset["image"]) - - if raw_tileset.get("imagewidth") is not None: - tileset.image_width = raw_tileset["imagewidth"] - - if raw_tileset.get("imageheight") is not None: - tileset.image_height = raw_tileset["imageheight"] - - if raw_tileset.get("backgroundcolor") is not None: - tileset.background_color = parse_color(raw_tileset["backgroundcolor"]) - - if raw_tileset.get("tileoffset") is not None: - tileset.tile_offset = _cast_tile_offset(raw_tileset["tileoffset"]) - - if raw_tileset.get("transparentcolor") is not None: - tileset.transparent_color = parse_color(raw_tileset["transparentcolor"]) - - if raw_tileset.get("grid") is not None: - tileset.grid = _cast_grid(raw_tileset["grid"]) - - if raw_tileset.get("properties") is not None: - tileset.properties = properties_.cast(raw_tileset["properties"]) - - if raw_tileset.get("tiles") is not None: - tiles = {} - for raw_tile in raw_tileset["tiles"]: - tiles[raw_tile["id"]] = _cast_tile(raw_tile, external_path=external_path) - tileset.tiles = tiles - - if raw_tileset.get("wangsets") is not None: - wangsets = [] - for raw_wangset in raw_tileset["wangsets"]: - wangsets.append(cast_wangset(raw_wangset)) - tileset.wang_sets = wangsets - - if raw_tileset.get("transformations") is not None: - tileset.transformations = _cast_transformations(raw_tileset["transformations"]) - - return tileset diff --git a/pytiled_parser/version.py b/pytiled_parser/version.py index b7bdf02..b06d5b0 100644 --- a/pytiled_parser/version.py +++ b/pytiled_parser/version.py @@ -1,3 +1,3 @@ """pytiled_parser version""" -__version__ = "1.5.4" +__version__ = "2.0.0-beta" diff --git a/pytiled_parser/wang_set.py b/pytiled_parser/wang_set.py index 011410f..9241742 100644 --- a/pytiled_parser/wang_set.py +++ b/pytiled_parser/wang_set.py @@ -1,11 +1,9 @@ from typing import Dict, List, Optional import attr -from typing_extensions import TypedDict -from . import properties as properties_ -from .common_types import Color -from .util import parse_color +from pytiled_parser.common_types import Color +from pytiled_parser.properties import Properties @attr.s(auto_attribs=True) @@ -22,7 +20,7 @@ class WangColor: name: str probability: float tile: int - properties: Optional[properties_.Properties] = None + properties: Optional[Properties] = None @attr.s(auto_attribs=True) @@ -33,100 +31,4 @@ class WangSet: wang_type: str wang_tiles: Dict[int, WangTile] wang_colors: List[WangColor] - properties: Optional[properties_.Properties] = None - - -class RawWangTile(TypedDict): - """ The keys and their types that appear in a Wang Tile JSON Object.""" - - tileid: int - # Tiled stores these IDs as a list represented like so: - # [top, top_right, right, bottom_right, bottom, bottom_left, left, top_left] - wangid: List[int] - - -class RawWangColor(TypedDict): - """ The keys and their types that appear in a Wang Color JSON Object.""" - - color: str - name: str - probability: float - tile: int - properties: List[properties_.RawProperty] - - -class RawWangSet(TypedDict): - """ The keys and their types that appear in a Wang Set JSON Object.""" - - colors: List[RawWangColor] - name: str - properties: List[properties_.RawProperty] - tile: int - type: str - wangtiles: List[RawWangTile] - - -def _cast_wang_tile(raw_wang_tile: RawWangTile) -> WangTile: - """Cast the raw wang tile into a pytiled_parser type - - Args: - raw_wang_tile: RawWangTile to be cast. - - Returns: - WangTile: A properly typed WangTile. - """ - return WangTile(tile_id=raw_wang_tile["tileid"], wang_id=raw_wang_tile["wangid"]) - - -def _cast_wang_color(raw_wang_color: RawWangColor) -> WangColor: - """Cast the raw wang color into a pytiled_parser type - - Args: - raw_wang_color: RawWangColor to be cast. - - Returns: - WangColor: A properly typed WangColor. - """ - wang_color = WangColor( - name=raw_wang_color["name"], - color=parse_color(raw_wang_color["color"]), - tile=raw_wang_color["tile"], - probability=raw_wang_color["probability"], - ) - - if raw_wang_color.get("properties") is not None: - wang_color.properties = properties_.cast(raw_wang_color["properties"]) - - return wang_color - - -def cast(raw_wangset: RawWangSet) -> WangSet: - """Cast the raw wangset into a pytiled_parser type - - Args: - raw_wangset: Raw Wangset to be cast. - - Returns: - WangSet: A properly typed WangSet. - """ - - colors = [] - for raw_wang_color in raw_wangset["colors"]: - colors.append(_cast_wang_color(raw_wang_color)) - - tiles = {} - for raw_wang_tile in raw_wangset["wangtiles"]: - tiles[raw_wang_tile["tileid"]] = _cast_wang_tile(raw_wang_tile) - - wangset = WangSet( - name=raw_wangset["name"], - tile=raw_wangset["tile"], - wang_type=raw_wangset["type"], - wang_colors=colors, - wang_tiles=tiles, - ) - - if raw_wangset.get("properties") is not None: - wangset.properties = properties_.cast(raw_wangset["properties"]) - - return wangset + properties: Optional[Properties] = None diff --git a/pytiled_parser/world.py b/pytiled_parser/world.py index 5d35322..797ff39 100644 --- a/pytiled_parser/world.py +++ b/pytiled_parser/world.py @@ -8,8 +8,9 @@ from typing import List import attr from typing_extensions import TypedDict -from .common_types import OrderedPair, Size -from .tiled_map import TiledMap, parse_map +from pytiled_parser.common_types import OrderedPair, Size +from pytiled_parser.parser import parse_map +from pytiled_parser.tiled_map import TiledMap @attr.s(auto_attribs=True) @@ -55,7 +56,7 @@ class RawWorld(TypedDict): onlyShowAdjacentMaps: bool -def _cast_world_map(raw_world_map: RawWorldMap, map_file: Path) -> WorldMap: +def _parse_world_map(raw_world_map: RawWorldMap, map_file: Path) -> WorldMap: """Parse the RawWorldMap into a WorldMap. Args: @@ -94,7 +95,7 @@ def parse_world(file: Path) -> World: if raw_world.get("maps"): for raw_map in raw_world["maps"]: map_path = Path(parent_dir / raw_map["fileName"]) - maps.append(_cast_world_map(raw_map, map_path)) + maps.append(_parse_world_map(raw_map, map_path)) if raw_world.get("patterns"): for raw_pattern in raw_world["patterns"]: @@ -131,7 +132,7 @@ def parse_world(file: Path) -> World: } map_path = Path(parent_dir / map_file) - maps.append(_cast_world_map(raw_world_map, map_path)) + maps.append(_parse_world_map(raw_world_map, map_path)) world = World(maps=maps) diff --git a/setup.cfg b/setup.cfg index 86ce232..2ccb360 100644 --- a/setup.cfg +++ b/setup.cfg @@ -44,7 +44,7 @@ tests = pytest pytest-cov black - pylint + flake8 mypy isort<5,>=4.2.5 @@ -104,3 +104,7 @@ strict_optional = True [mypy-tests.*] ignore_errors = True + +[flake8] +max-line-length = 88 +exclude = .git,__pycache__,__init__.py,.mypy_cache,.pytest_cache diff --git a/tests/test_layer.py b/tests/test_layer.py index 117761b..75cb733 100644 --- a/tests/test_layer.py +++ b/tests/test_layer.py @@ -6,7 +6,7 @@ from pathlib import Path import pytest -from pytiled_parser import layer +from pytiled_parser.parsers.json.layer import parse TESTS_DIR = Path(os.path.dirname(os.path.abspath(__file__))) TEST_DATA = TESTS_DIR / "test_data" @@ -39,6 +39,6 @@ def test_layer_integration(layer_test): with open(raw_layers_path) as raw_layers_file: raw_layers = json.load(raw_layers_file)["layers"] - layers = [layer.cast(raw_layer) for raw_layer in raw_layers] + layers = [parse(raw_layer) for raw_layer in raw_layers] assert layers == expected.EXPECTED diff --git a/tests/test_map.py b/tests/test_map.py index 0019238..68ad996 100644 --- a/tests/test_map.py +++ b/tests/test_map.py @@ -5,7 +5,7 @@ from pathlib import Path import pytest -from pytiled_parser import tiled_map +from pytiled_parser import parse_map TESTS_DIR = Path(os.path.dirname(os.path.abspath(__file__))) TEST_DATA = TESTS_DIR / "test_data" @@ -31,7 +31,7 @@ def test_map_integration(map_test): raw_maps_path = map_test / "map.json" - casted_map = tiled_map.parse_map(raw_maps_path) + casted_map = parse_map(raw_maps_path) expected.EXPECTED.map_file = casted_map.map_file assert casted_map == expected.EXPECTED diff --git a/tests/test_tiled_object.py b/tests/test_tiled_object.py index 10e71c4..eacbc3e 100644 --- a/tests/test_tiled_object.py +++ b/tests/test_tiled_object.py @@ -5,7 +5,17 @@ from pathlib import Path import pytest -from pytiled_parser import common_types, tiled_object +from pytiled_parser import common_types +from pytiled_parser.parsers.json.tiled_object import parse +from pytiled_parser.tiled_object import ( + Ellipse, + Point, + Polygon, + Polyline, + Rectangle, + Text, + Tile, +) ELLIPSES = [ ( @@ -23,7 +33,7 @@ ELLIPSES = [ "y":81.1913152210981 } """, - tiled_object.Ellipse( + Ellipse( id=6, size=common_types.Size(57.4013868364215, 18.5517790155735), name="name: ellipse", @@ -48,7 +58,7 @@ ELLIPSES = [ "y":53.9092872570194 } """, - tiled_object.Ellipse( + Ellipse( id=7, size=common_types.Size(6.32943048766625, 31.4288962146186), name="name: ellipse - invisible", @@ -73,7 +83,7 @@ ELLIPSES = [ "y":120.040923041946 } """, - tiled_object.Ellipse( + Ellipse( id=8, size=common_types.Size(29.6828464249176, 24.2264408321018), name="name: ellipse - rotated", @@ -98,7 +108,7 @@ ELLIPSES = [ "y":127.679890871888 } """, - tiled_object.Ellipse( + Ellipse( id=29, name="name: ellipse - no width or height", rotation=0, @@ -124,7 +134,7 @@ RECTANGLES = [ "y":23.571672160964 } """, - tiled_object.Rectangle( + Rectangle( id=1, size=common_types.Size(45.3972945322269, 41.4686825053996), name="name: rectangle", @@ -148,7 +158,7 @@ RECTANGLES = [ "y":91.0128452881664 } """, - tiled_object.Rectangle( + Rectangle( id=4, size=common_types.Size(30.9923837671934, 32.7384335568944), name="name: rectangle - invisible", @@ -172,7 +182,7 @@ RECTANGLES = [ "y":23.3534159372513 } """, - tiled_object.Rectangle( + Rectangle( id=5, size=common_types.Size(10, 22), name="name: rectangle - rotated", @@ -196,7 +206,7 @@ RECTANGLES = [ "y":53.4727748095942 } """, - tiled_object.Rectangle( + Rectangle( id=28, size=common_types.Size(0, 0), name="name: rectangle - no width or height", @@ -251,7 +261,7 @@ RECTANGLES = [ "y":131.826759122428 } """, - tiled_object.Rectangle( + Rectangle( id=30, size=common_types.Size(21.170853700125, 13.7501420938956), name="name: rectangle - properties", @@ -287,7 +297,7 @@ POINTS = [ "y":82.9373650107991 } """, - tiled_object.Point( + Point( id=2, name="name: point", rotation=0, @@ -311,7 +321,7 @@ POINTS = [ "y":95.8144822098443 } """, - tiled_object.Point( + Point( id=3, name="name: point invisible", rotation=0, @@ -338,7 +348,7 @@ TILES = [ "y":48.3019211094691 } """, - tiled_object.Tile( + Tile( id=13, size=common_types.Size(32, 32), name="name: tile", @@ -364,7 +374,7 @@ TILES = [ "y":168.779356598841 } """, - tiled_object.Tile( + Tile( id=14, size=common_types.Size(32, 32), name="name: tile - invisible", @@ -390,7 +400,7 @@ TILES = [ "y":59.8695009662385 } """, - tiled_object.Tile( + Tile( id=15, size=common_types.Size(32, 32), name="name: tile - horizontal flipped", @@ -416,7 +426,7 @@ TILES = [ "y":60.742525861089 } """, - tiled_object.Tile( + Tile( id=16, size=common_types.Size(32, 32), name="name: tile - vertical flipped", @@ -442,7 +452,7 @@ TILES = [ "y":95.6635216551097 } """, - tiled_object.Tile( + Tile( id=17, size=common_types.Size(32, 32), name="name: tile - both flipped", @@ -468,7 +478,7 @@ TILES = [ "y":142.62 } """, - tiled_object.Tile( + Tile( id=18, size=common_types.Size(32, 32), name="name: tile - rotated", @@ -517,7 +527,7 @@ POLYGONS = [ "y":38.6313515971354 } """, - tiled_object.Polygon( + Polygon( id=9, name="name: polygon", points=[ @@ -560,7 +570,7 @@ POLYGONS = [ "y":24.4446970558145 } """, - tiled_object.Polygon( + Polygon( id=10, name="name: polygon - invisible", points=[ @@ -613,7 +623,7 @@ POLYGONS = [ "y":19.8613163578493 } """, - tiled_object.Polygon( + Polygon( id=11, name="name: polygon - rotated", points=[ @@ -660,7 +670,7 @@ POLYLINES = [ "y":90.1398203933159 } """, - tiled_object.Polyline( + Polyline( id=12, name="name: polyline", points=[ @@ -701,7 +711,7 @@ POLYLINES = [ "y":163.333333333333 } """, - tiled_object.Polyline( + Polyline( id=31, name="name: polyline - invisible", points=[ @@ -742,7 +752,7 @@ POLYLINES = [ "y":128.666666666667 } """, - tiled_object.Polyline( + Polyline( id=32, name="name: polyline - rotated", points=[ @@ -778,7 +788,7 @@ TEXTS = [ "y":93.2986813686484 } """, - tiled_object.Text( + Text( id=19, name="name: text", text="Hello World", @@ -809,7 +819,7 @@ TEXTS = [ "y":112.068716607935 } """, - tiled_object.Text( + Text( id=20, name="name: text - invisible", text="Hello World", @@ -840,7 +850,7 @@ TEXTS = [ "y":78.4572581561896 } """, - tiled_object.Text( + Text( id=21, name="name: text - rotated", text="Hello World", @@ -874,7 +884,7 @@ TEXTS = [ "y":101.592417869728 } """, - tiled_object.Text( + Text( id=22, name="name: text - different font", text="Hello World", @@ -907,7 +917,7 @@ TEXTS = [ "y":154.192167784472 } """, - tiled_object.Text( + Text( id=23, name="name: text - no word wrap", text="Hello World", @@ -939,7 +949,7 @@ TEXTS = [ "y":1.19455496191883 } """, - tiled_object.Text( + Text( id=24, name="name: text - right bottom align", text="Hello World", @@ -973,7 +983,7 @@ TEXTS = [ "y": 3.81362964647039 } """, - tiled_object.Text( + Text( id=25, name="text: center center align", rotation=0, @@ -1006,7 +1016,7 @@ TEXTS = [ "y": 60.7785040354666 } """, - tiled_object.Text( + Text( id=26, name="name: text - justified", rotation=0, @@ -1038,7 +1048,7 @@ TEXTS = [ "y": 130.620495623508 } """, - tiled_object.Text( + Text( id=27, name="name: text - red", rotation=0, @@ -1075,7 +1085,7 @@ TEXTS = [ "y":22 } """, - tiled_object.Text( + Text( id=31, name="name: text - font options", rotation=0, @@ -1100,7 +1110,7 @@ OBJECTS = ELLIPSES + RECTANGLES + POINTS + TILES + POLYGONS + POLYLINES + TEXTS @pytest.mark.parametrize("raw_object_json,expected", OBJECTS) def test_parse_layer(raw_object_json, expected): raw_object = json.loads(raw_object_json) - result = tiled_object.cast(raw_object) + result = parse(raw_object) assert result == expected @@ -1118,4 +1128,4 @@ def test_parse_no_parent_dir(): json_object = json.loads(raw_object) with pytest.raises(RuntimeError): - tiled_object.cast(json_object) + parse(json_object) diff --git a/tests/test_tileset.py b/tests/test_tileset.py index d24e9d5..c5b8f7c 100644 --- a/tests/test_tileset.py +++ b/tests/test_tileset.py @@ -6,7 +6,7 @@ from pathlib import Path import pytest -from pytiled_parser import tileset +from pytiled_parser.parsers.json.tileset import parse TESTS_DIR = Path(os.path.dirname(os.path.abspath(__file__))) TEST_DATA = TESTS_DIR / "test_data" @@ -39,6 +39,6 @@ def test_tilesets_integration(tileset_dir): raw_tileset_path = tileset_dir / "tileset.json" with open(raw_tileset_path) as raw_tileset: - tileset_ = tileset.cast(json.loads(raw_tileset.read()), 1) + tileset_ = parse(json.loads(raw_tileset.read()), 1) assert tileset_ == expected.EXPECTED