Work on TMX parser. It is very not done

This commit is contained in:
Darren Eberly
2021-12-15 20:59:25 -05:00
parent 45e0784d3d
commit d653ff63a3
7 changed files with 474 additions and 1 deletions

View File

@@ -13,7 +13,7 @@ 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 .parser import parse_map, parse_tmx
from .properties import Properties
from .tiled_map import TiledMap
from .tileset import Tile, Tileset

View File

@@ -1,6 +1,7 @@
from pathlib import Path
from pytiled_parser.parsers.json.tiled_map import parse as json_map_parse
from pytiled_parser.parsers.tmx.tiled_map import parse as tmx_map_parse
from pytiled_parser.tiled_map import TiledMap
@@ -15,3 +16,7 @@ def parse_map(file: Path) -> TiledMap:
"""
# I have no idea why, but mypy thinks this function returns "Any"
return json_map_parse(file) # type: ignore
def parse_tmx(file: Path) -> TiledMap:
return tmx_map_parse(file) # type: ignore

View File

@@ -0,0 +1,117 @@
"""Layer parsing for the TMX Map Format.
"""
import xml.etree.ElementTree as etree
from pathlib import Path
from typing import Optional
from pytiled_parser.common_types import OrderedPair, Size
from pytiled_parser.layer import ImageLayer, Layer
from pytiled_parser.parsers.tmx.properties import parse as parse_properties
from pytiled_parser.util import parse_color
def _parse_common(raw_layer: etree.Element) -> 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: XML Element to get common attributes from
Returns:
Layer: The attributes in common of all layer types
"""
common = Layer(
name=raw_layer.attrib["name"],
opacity=float(raw_layer.attrib["opacity"]),
visible=bool(int(raw_layer.attrib["visible"])),
)
if raw_layer.attrib.get("id") is not None:
common.id = int(raw_layer.attrib["id"])
if raw_layer.attrib.get("offsetx") is not None:
common.offset = OrderedPair(
float(raw_layer.attrib["offsetx"]), float(raw_layer.attrib["offsety"])
)
properties_element = raw_layer.find("./properties")
if properties_element:
common.properties = parse_properties(properties_element)
parallax = [1.0, 1.0]
if raw_layer.attrib.get("parallaxx") is not None:
parallax[0] = float(raw_layer.attrib["parallaxx"])
if raw_layer.attrib.get("parallaxy") is not None:
parallax[1] = float(raw_layer.attrib["parallaxy"])
common.parallax_factor = OrderedPair(parallax[0], parallax[1])
if raw_layer.attrib.get("tintcolor") is not None:
common.tint_color = parse_color(raw_layer.attrib["tintcolor"])
return common
def _parse_image_layer(raw_layer: etree.Element) -> ImageLayer:
"""Parse the raw_layer to an ImageLayer.
Args:
raw_layer: XML Element to be parsed to an ImageLayer.
Returns:
ImageLayer: The ImageLayer created from raw_layer
"""
image_element = raw_layer.find("./image")
if image_element:
source = Path(image_element.attrib["source"])
width = int(image_element.attrib["width"])
height = int(image_element.attrib["height"])
transparent_color = None
if image_element.attrib.get("trans") is not None:
transparent_color = parse_color(image_element.attrib["trans"])
return ImageLayer(
image=source,
size=Size(width, height),
transparent_color=transparent_color,
**_parse_common(raw_layer).__dict__,
)
raise RuntimeError("Tried to parse an image layer that doesn't have an image!")
def parse(
raw_layer: etree.Element,
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.tag
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_ == "layer":
return _parse_tile_layer(raw_layer)
raise RuntimeError(f"An invalid layer type of {type_} was supplied")

View File

@@ -0,0 +1,32 @@
import xml.etree.ElementTree as etree
from pathlib import Path
from typing import List, Union, cast
from pytiled_parser.properties import Properties, Property
from pytiled_parser.util import parse_color
def parse(raw_properties: etree.Element) -> Properties:
final: Properties = {}
value: Property
for raw_property in raw_properties.findall("./property"):
type_ = raw_property.attrib["type"]
value_ = raw_property.attrib["value"]
if type_ == "file":
value = Path(value_)
elif type_ == "color":
value = parse_color(value_)
elif type_ == "int" or type_ == "float":
value = float(value_)
elif type_ == "bool":
if value_ == "true":
value = True
else:
value = False
else:
value = value_
final[raw_property.attrib["name"]] = value
return final

View File

@@ -0,0 +1,59 @@
import xml.etree.ElementTree as etree
from pathlib import Path
from pytiled_parser.common_types import OrderedPair, Size
from pytiled_parser.parsers.tmx.tileset import parse as parse_tileset
from pytiled_parser.tiled_map import TiledMap, TilesetDict
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_map = etree.parse(map_file).getroot()
parent_dir = file.parent
raw_tilesets = raw_map.findall("./tileset")
tilesets: TilesetDict = {}
for raw_tileset in raw_tilesets:
if raw_tileset.attrib.get("source") is not None:
# Is an external Tileset
tileset_path = Path(parent_dir / raw_tileset.attrib["source"])
with open(tileset_path) as tileset_file:
raw_tileset = etree.parse(tileset_file).getroot()
tilesets[int(raw_tileset.attrib["firstgid"])] = parse_tileset(
raw_tileset,
int(raw_tileset.attrib["firstgid"]),
external_path=tileset_path.parent,
)
else:
# Is an embedded Tileset
tilesets[int(raw_tileset.attrib["firstgid"])] = parse_tileset(
raw_tileset, int(raw_tileset.attrib["firstgid"])
)
map_ = TiledMap(
map_file=file,
infinite=bool(int(raw_map.attrib["infinite"])),
layers=[parse_layer(layer_, parent_dir) for layer_ in raw_tiled_map["layers"]],
map_size=Size(int(raw_map.attrib["width"]), int(raw_map.attrib["height"])),
next_layer_id=int(raw_map.attrib["nextlayerid"]),
next_object_id=int(raw_map.attrib["nextobjectid"]),
orientation=raw_map.attrib["orientation"],
render_order=raw_map.attrib["renderorder"],
tiled_version=raw_map.attrib["tiledversion"],
tile_size=Size(
int(raw_map.attrib["tilewidth"]), int(raw_map.attrib["tileheight"])
),
tilesets=tilesets,
version=raw_map.attrib["version"],
)

View File

@@ -0,0 +1,186 @@
import xml.etree.ElementTree as etree
from pathlib import Path
from typing import Optional
from pytiled_parser.common_types import OrderedPair
from pytiled_parser.parsers.tmx.properties import parse as parse_properties
from pytiled_parser.parsers.tmx.wang_set import parse as parse_wangset
from pytiled_parser.tileset import Frame, Grid, Tile, Tileset, Transformations
from pytiled_parser.util import parse_color
def _parse_frame(raw_frame: etree.Element) -> Frame:
"""Parse the raw_frame to a Frame object.
Args:
raw_frame: XML Element to be parsed to a Frame
Returns:
Frame: The Frame created from the raw_frame
"""
return Frame(
duration=int(raw_frame.attrib["duration"]),
tile_id=int(raw_frame.attrib["tileid"]),
)
def _parse_grid(raw_grid: etree.Element) -> Grid:
"""Parse the raw_grid to a Grid object.
Args:
raw_grid: XML Element to be parsed to a Grid
Returns:
Grid: The Grid created from the raw_grid
"""
return Grid(
orientation=raw_grid.attrib["orientation"],
width=int(raw_grid.attrib["width"]),
height=int(raw_grid.attrib["height"]),
)
def _parse_transformations(raw_transformations: etree.Element) -> Transformations:
"""Parse the raw_transformations to a Transformations object.
Args:
raw_transformations: XML Element to be parsed to a Transformations
Returns:
Transformations: The Transformations created from the raw_transformations
"""
return Transformations(
hflip=bool(int(raw_transformations.attrib["hflip"])),
vflip=bool(int(raw_transformations.attrib["vflip"])),
rotate=bool(int(raw_transformations.attrib["rotate"])),
prefer_untransformed=bool(
int(raw_transformations.attrib["preferuntransformed"])
),
)
def _parse_tile(raw_tile: etree.Element, external_path: Optional[Path] = None) -> Tile:
"""Parse the raw_tile to a Tile object.
Args:
raw_tile: XML Element to be parsed to a Tile
Returns:
Tile: The Tile created from the raw_tile
"""
tile = Tile(id=int(raw_tile.attrib["id"]))
if raw_tile.attrib.get("type") is not None:
tile.type = raw_tile.attrib["type"]
animation_element = raw_tile.find("./animation")
if animation_element:
tile.animation = []
for raw_frame in animation_element.findall("./frame"):
tile.animation.append(_parse_frame(raw_frame))
properties_element = raw_tile.find("./properties")
if properties_element:
tile.properties = parse_properties(properties_element)
image_element = raw_tile.find("./image")
if image_element:
if external_path:
tile.image = (
Path(external_path / image_element.attrib["source"])
.absolute()
.resolve()
)
else:
tile.image = Path(image_element.attrib["source"])
tile.image_width = int(image_element.attrib["width"])
tile.image_height = int(image_element.attrib["height"])
return tile
def parse(
raw_tileset: etree.Element,
firstgid: int,
external_path: Optional[Path] = None,
) -> Tileset:
tileset = Tileset(
name=raw_tileset.attrib["name"],
tile_count=int(raw_tileset.attrib["tilecount"]),
tile_width=int(raw_tileset.attrib["tilewidth"]),
tile_height=int(raw_tileset.attrib["tileheight"]),
columns=int(raw_tileset.attrib["columns"]),
spacing=int(raw_tileset.attrib["spacing"]),
margin=int(raw_tileset.attrib["margin"]),
firstgid=firstgid,
)
if raw_tileset.attrib.get("version") is not None:
tileset.version = raw_tileset.attrib["version"]
if raw_tileset.attrib.get("tiledversion") is not None:
tileset.tiled_version = raw_tileset.attrib["tiledversion"]
if raw_tileset.attrib.get("backgroundcolor") is not None:
tileset.background_color = parse_color(raw_tileset.attrib["backgroundcolor"])
image_element = raw_tileset.find("./image")
if image_element:
if external_path:
tileset.image = (
Path(external_path / image_element.attrib["source"])
.absolute()
.resolve()
)
else:
tileset.image = Path(image_element.attrib["source"])
tileset.image_width = int(image_element.attrib["width"])
tileset.image_height = int(image_element.attrib["height"])
if image_element.attrib.get("trans") is not None:
my_string = image_element.attrib["trans"]
if my_string[0] != "#":
my_string = f"#{my_string}"
tileset.transparent_color = parse_color(my_string)
pass
tileoffset_element = raw_tileset.find("./tileoffset")
if tileoffset_element:
tileset.tile_offset = OrderedPair(
int(tileoffset_element.attrib["x"]), int(tileoffset_element.attrib["y"])
)
grid_element = raw_tileset.find("./grid")
if grid_element:
tileset.grid = _parse_grid(grid_element)
properties_element = raw_tileset.find("./properties")
if properties_element:
tileset.properties = parse_properties(properties_element)
tiles = {}
for tile_element in raw_tileset.findall("./tiles"):
tiles[int(tile_element.attrib["id"])] = _parse_tile(
tile_element, external_path=external_path
)
if tiles:
tileset.tiles = tiles
wangsets_element = raw_tileset.find("./wangsets")
if wangsets_element:
wangsets = []
for raw_wangset in wangsets_element.findall("./wangset"):
wangsets.append(parse_wangset(raw_wangset))
tileset.wang_sets = wangsets
transformations_element = raw_tileset.find("./transformations")
if transformations_element:
tileset.transformations = _parse_transformations(transformations_element)
return tileset

View File

@@ -0,0 +1,74 @@
import xml.etree.ElementTree as etree
from pytiled_parser.parsers.tmx.properties import parse as parse_properties
from pytiled_parser.util import parse_color
from pytiled_parser.wang_set import WangColor, WangSet, WangTile
def _parse_wang_tile(raw_wang_tile: etree.Element) -> WangTile:
"""Parse the raw wang tile into a pytiled_parser type
Args:
raw_wang_tile: XML Element to be parsed.
Returns:
WangTile: A properly typed WangTile.
"""
ids = [int(v.strip()) for v in raw_wang_tile.attrib["wangid"].split(",")]
return WangTile(tile_id=int(raw_wang_tile.attrib["tileid"]), wang_id=ids)
def _parse_wang_color(raw_wang_color: etree.Element) -> WangColor:
"""Parse the raw wang color into a pytiled_parser type
Args:
raw_wang_color: XML Element to be parsed.
Returns:
WangColor: A properly typed WangColor.
"""
wang_color = WangColor(
name=raw_wang_color.attrib["name"],
color=parse_color(raw_wang_color.attrib["color"]),
tile=int(raw_wang_color.attrib["tile"]),
probability=float(raw_wang_color.attrib["probability"]),
)
properties = raw_wang_color.find("./properties")
if properties:
wang_color.properties = parse_properties(properties)
return wang_color
def parse(raw_wangset: etree.Element) -> WangSet:
"""Parse the raw wangset into a pytiled_parser type
Args:
raw_wangset: XML Element to be parsed.
Returns:
WangSet: A properly typed WangSet.
"""
colors = []
for raw_wang_color in raw_wangset.findall("./wangcolor"):
colors.append(_parse_wang_color(raw_wang_color))
tiles = {}
for raw_wang_tile in raw_wangset.findall("./wangtile"):
tiles[int(raw_wang_tile.attrib["tileid"])] = _parse_wang_tile(raw_wang_tile)
wangset = WangSet(
name=raw_wangset.attrib["name"],
tile=int(raw_wangset.attrib["tile"]),
wang_type=raw_wangset.attrib["type"],
wang_colors=colors,
wang_tiles=tiles,
)
properties = raw_wangset.find("./properties")
if properties:
wangset.properties = parse_properties(properties)
return wangset