mirror of
https://github.com/OMGeeky/pytiled_parser.git
synced 2025-12-26 17:02:28 +01:00
Initial 2.0 refactor
This commit is contained in:
@@ -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__
|
||||
|
||||
@@ -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")
|
||||
|
||||
17
pytiled_parser/parser.py
Normal file
17
pytiled_parser/parser.py
Normal file
@@ -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
|
||||
364
pytiled_parser/parsers/json/layer.py
Normal file
364
pytiled_parser/parsers/json/layer.py
Normal file
@@ -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")
|
||||
48
pytiled_parser/parsers/json/properties.py
Normal file
48
pytiled_parser/parsers/json/properties.py
Normal file
@@ -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
|
||||
153
pytiled_parser/parsers/json/tiled_map.py
Normal file
153
pytiled_parser/parsers/json/tiled_map.py
Normal file
@@ -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_
|
||||
321
pytiled_parser/parsers/json/tiled_object.py
Normal file
321
pytiled_parser/parsers/json/tiled_object.py
Normal file
@@ -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)
|
||||
272
pytiled_parser/parsers/json/tileset.py
Normal file
272
pytiled_parser/parsers/json/tileset.py
Normal file
@@ -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
|
||||
104
pytiled_parser/parsers/json/wang_set.py
Normal file
104
pytiled_parser/parsers/json/wang_set.py
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"""pytiled_parser version"""
|
||||
|
||||
__version__ = "1.5.4"
|
||||
__version__ = "2.0.0-beta"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user