mirror of
https://github.com/OMGeeky/pytiled_parser.git
synced 2025-12-26 17:02:28 +01:00
Work on TMX parser. It is very not done
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
117
pytiled_parser/parsers/tmx/layer.py
Normal file
117
pytiled_parser/parsers/tmx/layer.py
Normal 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")
|
||||
32
pytiled_parser/parsers/tmx/properties.py
Normal file
32
pytiled_parser/parsers/tmx/properties.py
Normal 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
|
||||
59
pytiled_parser/parsers/tmx/tiled_map.py
Normal file
59
pytiled_parser/parsers/tmx/tiled_map.py
Normal 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"],
|
||||
)
|
||||
186
pytiled_parser/parsers/tmx/tileset.py
Normal file
186
pytiled_parser/parsers/tmx/tileset.py
Normal 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
|
||||
74
pytiled_parser/parsers/tmx/wang_set.py
Normal file
74
pytiled_parser/parsers/tmx/wang_set.py
Normal 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
|
||||
Reference in New Issue
Block a user