Files
pytiled_parser/pytiled_parser/parsers/json/tileset.py
2022-08-02 20:28:19 -04:00

306 lines
9.2 KiB
Python

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
RawFrame = TypedDict("RawFrame", {
"duration": int,
"tileid": int
})
RawFrame.__doc__ = """
The keys and their types that appear in a Frame JSON Object.
"""
RawTileOffset = TypedDict("RawTileOffset", {
"x": int,
"y": int
})
RawTileOffset.__doc__ = """
The keys and their types that appear in a TileOffset JSON Object.
"""
RawTransformations = TypedDict("RawTransformations", {
"hflip": bool,
"vflip": bool,
"rotate": bool,
"preferuntransformed": bool
})
RawTransformations.__doc__ = """
The keys and their types that appear in a Transformations JSON Object.
"""
RawTile = TypedDict("RawTile", {
"animation": List[RawFrame],
"class": str,
"id": int,
"image": str,
"imageheight": int,
"imagewidth": int,
"opacity": float,
"type": str,
"properties": List[RawProperty],
"objectgroup": RawLayer,
})
RawTile.__docs__ = """
The keys and their types that appear in a Tile JSON Object.
"""
RawGrid = TypedDict("RawGrid", {
"height": int,
"width": int,
"orientation": str
})
RawGrid.__doc__ = """
The keys and their types that appear in a Grid JSON Object.
"""
RawTileSet = TypedDict("RawTileSet", {
"backgroundcolor": str,
"class": str,
"columns": int,
"firstgid": int,
"grid": RawGrid,
"image": str,
"imageheight": int,
"imagewidth": int,
"margin": int,
"name": str,
"properties": List[RawProperty],
"objectalignment": str,
"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]
})
RawTileSet.__doc__ = """
The keys and their types that appear in a TileSet JSON Object.
"""
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"])
# These are ignored from coverage because there does not exist a scenario where
# image is set, but these aren't, so the branches will never fully be hit.
# However, leaving these checks in place is nice to prevent fatal errors on
# a manually edited map that has an "incorrect" but not "unusable" structure
if raw_tile.get("imagewidth") is not None: # pragma: no cover
tile.image_width = raw_tile["imagewidth"]
if raw_tile.get("imageheight") is not None: # pragma: no cover
tile.image_height = raw_tile["imageheight"]
if raw_tile.get("type") is not None:
tile.class_ = raw_tile["type"]
if raw_tile.get("class") is not None:
tile.class_ = raw_tile["class"]
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:
# This is here to support old versions of Tiled Maps. It's a pain
# to keep old versions in the test data and not update them with the
# rest so I'm excluding this from coverage. In reality it's probably
# not needed. Tiled hasn't been using floats for the version for a long time
if isinstance(raw_tileset["version"], float): # pragma: no cover
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"])
# See above note about imagewidth and imageheight on parse_tile function
# for an explanation on why these are ignored
if raw_tileset.get("imagewidth") is not None: # pragma: no cover
tileset.image_width = raw_tileset["imagewidth"]
if raw_tileset.get("imageheight") is not None: # pragma: no cover
tileset.image_height = raw_tileset["imageheight"]
if raw_tileset.get("objectalignment") is not None:
tileset.alignment = raw_tileset["objectalignment"]
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"])
if raw_tileset.get("class") is not None:
tileset.class_ = raw_tileset["class"]
return tileset