mirror of
https://github.com/OMGeeky/pytiled_parser.git
synced 2025-12-27 14:52:15 +01:00
yay
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
from . import utilities
|
||||
from . import objects
|
||||
|
||||
from .parser import parse_tile_map
|
||||
from .xml_parser import parse_tile_map
|
||||
|
||||
@@ -14,18 +14,6 @@ import xml.etree.ElementTree as etree
|
||||
from typing import NamedTuple, Union, Optional, List, Dict
|
||||
|
||||
|
||||
class EncodingError(Exception):
|
||||
"""Tmx layer encoding is of an unknown type."""
|
||||
|
||||
|
||||
class TileNotFoundError(Exception):
|
||||
"""Tile not found in tileset."""
|
||||
|
||||
|
||||
class ImageNotFoundError(Exception):
|
||||
"""Image not found."""
|
||||
|
||||
|
||||
class Color(NamedTuple):
|
||||
"""Color object.
|
||||
|
||||
@@ -185,44 +173,35 @@ class TileTerrain:
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class _LayerTypeBase:
|
||||
id: int # pylint: disable=C0103
|
||||
name: str
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class _LayerTypeDefaults:
|
||||
offset: OrderedPair = OrderedPair(0, 0)
|
||||
opacity: int = 0xFF
|
||||
|
||||
properties: Optional[Properties] = None
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class LayerType(_LayerTypeDefaults, _LayerTypeBase):
|
||||
"""
|
||||
Class that all layer classes inherit from.
|
||||
|
||||
Not to be directly used.
|
||||
class Layer:
|
||||
"""Class that all layers inherret from.
|
||||
|
||||
Args:
|
||||
:layer_element (etree.Element): Element to be parsed into a
|
||||
LayerType object.
|
||||
|
||||
Attributes:
|
||||
:id (int): Unique ID of the layer. Each layer that added to a map
|
||||
gets a unique id. Even if a layer is deleted, no layer ever gets
|
||||
the same ID.
|
||||
:name (Optional[str):] The name of the layer object.
|
||||
:offset (OrderedPair): Rendering offset of the layer object in
|
||||
pixels. (default: (0, 0).
|
||||
:opacity (int): Value between 0 and 255 to determine opacity. NOTE:
|
||||
this value is converted from a float provided by Tiled, so some
|
||||
precision is lost.
|
||||
:properties (Optional[Properties]): Properties object for layer
|
||||
object.
|
||||
id: Unique ID of the layer. Each layer that added to a map gets a
|
||||
unique id. Even if a layer is deleted, no layer ever gets the same
|
||||
ID.
|
||||
name: The name of the layer object.
|
||||
tiled_objects: List of tiled_objects in the layer.
|
||||
offset: Rendering offset of the layer object in pixels.
|
||||
opacity: Decimal value between 0 and 1 to determine opacity. 1 is
|
||||
completely opaque, 0 is completely transparent.
|
||||
properties: Properties for the layer.
|
||||
color: The color used to display the objects in this group.
|
||||
FIXME: editor only?
|
||||
draworder: Whether the objects are drawn according to the order of the
|
||||
object elements in the object group element ('manual'), or sorted
|
||||
by their y-coordinate ('topdown'). Defaults to 'topdown'. See:
|
||||
https://doc.mapeditor.org/en/stable/manual/objects/#changing-stacking-order
|
||||
for more info.
|
||||
"""
|
||||
|
||||
id: int
|
||||
name: str
|
||||
|
||||
offset: Optional[OrderedPair]
|
||||
opacity: Optional[float]
|
||||
properties: Optional[Properties]
|
||||
|
||||
|
||||
LayerData = Union[List[List[int]], List[Chunk]]
|
||||
"""
|
||||
@@ -234,26 +213,22 @@ Either a 2 dimensional array of integers representing the global tile IDs
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class _LayerBase:
|
||||
size: Size
|
||||
data: LayerData
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Layer(LayerType, _LayerBase):
|
||||
"""
|
||||
Map layer object.
|
||||
class TileLayer(Layer):
|
||||
"""Tile map layer containing tiles.
|
||||
|
||||
See: https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#layer
|
||||
|
||||
Attributes:
|
||||
:size (Size): The width of the layer in tiles. Always the same
|
||||
as the map width for not infitite maps.
|
||||
:data (LayerData): Either an 2 dimensional array of integers
|
||||
representing the global tile IDs for the map layer, or a list of
|
||||
chunks for an infinite map.
|
||||
Args:
|
||||
size: The width of the layer in tiles. The same as the map width
|
||||
unless map is infitite.
|
||||
data: Either an 2 dimensional array of integers representing the
|
||||
global tile IDs for the map layer, or a list of chunks for an
|
||||
infinite map.
|
||||
"""
|
||||
|
||||
size: Size
|
||||
data: LayerData
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class _TiledObjectBase:
|
||||
@@ -265,7 +240,7 @@ class _TiledObjectBase:
|
||||
class _TiledObjectDefaults:
|
||||
size: Size = Size(0, 0)
|
||||
rotation: int = 0
|
||||
opacity: int = 0xFF
|
||||
opacity: float = 1
|
||||
|
||||
name: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
@@ -277,7 +252,7 @@ class _TiledObjectDefaults:
|
||||
@dataclasses.dataclass
|
||||
class TiledObject(_TiledObjectDefaults, _TiledObjectBase):
|
||||
"""
|
||||
TiledObjectGroup object.
|
||||
TiledObject object.
|
||||
|
||||
See:
|
||||
https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#object
|
||||
@@ -423,48 +398,36 @@ class TextObject(TiledObject, _TextObjectDefaults, _TextObjectBase):
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class _ObjectGroupBase(_LayerTypeBase):
|
||||
objects: List[TiledObject]
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class _ObjectGroupDefaults(_LayerTypeDefaults):
|
||||
color: Optional[Color] = None
|
||||
draw_order: Optional[str] = "topdown"
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ObjectGroup(LayerType, _ObjectGroupDefaults, _ObjectGroupBase):
|
||||
class ObjectLayer(Layer):
|
||||
"""
|
||||
TiledObject Group Object.
|
||||
|
||||
The object group is in fact a map layer, and is hence called \
|
||||
“object layer” in Tiled.
|
||||
|
||||
See: \
|
||||
https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#objectgroup
|
||||
See:
|
||||
https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#objectgroup
|
||||
|
||||
Attributes:
|
||||
:color (Optional[Color]): The color used to display the objects
|
||||
in this group. FIXME: editor only?
|
||||
:draworder (str): Whether the objects are drawn according to the
|
||||
order of the object elements in the object group element
|
||||
('manual'), or sorted by their y-coordinate ('topdown'). Defaults
|
||||
to 'topdown'. See:
|
||||
Args:
|
||||
tiled_objects: List of tiled_objects in the layer.
|
||||
offset: Rendering offset of the layer object in pixels.
|
||||
color: The color used to display the objects in this group.
|
||||
FIXME: editor only?
|
||||
draworder: Whether the objects are drawn according to the order of the
|
||||
object elements in the object group element ('manual'), or sorted
|
||||
by their y-coordinate ('topdown'). Defaults to 'topdown'. See:
|
||||
https://doc.mapeditor.org/en/stable/manual/objects/#changing-stacking-order
|
||||
for more info.
|
||||
:objects (Dict[int, TiledObject]): Dict TiledObject objects by
|
||||
TiledObject.id.
|
||||
"""
|
||||
|
||||
tiled_objects: List[TiledObject]
|
||||
|
||||
@dataclasses.dataclass
|
||||
class _LayerGroupBase(_LayerTypeBase):
|
||||
layers: Optional[List[LayerType]]
|
||||
color: Optional[Color] = None
|
||||
draw_order: Optional[str] = "topdown"
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class LayerGroup(LayerType):
|
||||
class LayerGroup(Layer):
|
||||
"""
|
||||
Layer Group.
|
||||
|
||||
@@ -479,6 +442,8 @@ class LayerGroup(LayerType):
|
||||
|
||||
"""
|
||||
|
||||
layers: Optional[List[Union["LayerGroup", Layer, ObjectLayer]]]
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Hitbox:
|
||||
@@ -608,7 +573,7 @@ class TileMap:
|
||||
next_object_id: int
|
||||
|
||||
tile_sets: TileSetDict
|
||||
layers: List[LayerType]
|
||||
layers: List[Layer]
|
||||
|
||||
hex_side_length: Optional[int] = None
|
||||
stagger_axis: Optional[int] = None
|
||||
|
||||
@@ -6,15 +6,16 @@ import zlib
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from typing import Dict, List, Optional, Union
|
||||
from typing import Callable, Dict, List, Optional, Tuple, Union
|
||||
import xml.etree.ElementTree as etree
|
||||
|
||||
import pytiled_parser.objects as objects
|
||||
import pytiled_parser.utilities as utilities
|
||||
|
||||
import xml.etree.ElementTree as etree
|
||||
|
||||
|
||||
def _decode_base64_data(data_text, compression, layer_width):
|
||||
def _decode_base64_data(
|
||||
data_text: str, compression: Optional[str], layer_width: int
|
||||
) -> List[List[int]]:
|
||||
tile_grid: List[List[int]] = [[]]
|
||||
|
||||
unencoded_data = base64.b64decode(data_text)
|
||||
@@ -48,13 +49,13 @@ def _decode_base64_data(data_text, compression, layer_width):
|
||||
return tile_grid
|
||||
|
||||
|
||||
def _decode_csv_layer(data_text):
|
||||
def _decode_csv_layer(data_text: str) -> List[List[int]]:
|
||||
"""Decodes csv encoded layer data.
|
||||
|
||||
Credit:
|
||||
"""
|
||||
tile_grid = []
|
||||
lines = data_text.split("\n")
|
||||
lines: List[str] = data_text.split("\n")
|
||||
# remove erronious empty lists due to a newline being on both ends of text
|
||||
lines = lines[1:]
|
||||
lines = lines[:-1]
|
||||
@@ -97,9 +98,9 @@ def _decode_data(
|
||||
raise ValueError("{compression} is not a valid compression type")
|
||||
|
||||
try:
|
||||
data_text = element.text # type: ignore
|
||||
data_text: str = element.text # type: ignore
|
||||
except AttributeError:
|
||||
raise AttributeError("{element} lacks layer data.")
|
||||
raise AttributeError(f"{element} lacks layer data.")
|
||||
|
||||
if encoding == "csv":
|
||||
return _decode_csv_layer(data_text)
|
||||
@@ -107,7 +108,9 @@ def _decode_data(
|
||||
return _decode_base64_data(data_text, compression, layer_width)
|
||||
|
||||
|
||||
def _parse_data(element: etree.Element, layer_width: int) -> objects.LayerData:
|
||||
def _parse_data(
|
||||
element: etree.Element, layer_width: int
|
||||
) -> objects.LayerData:
|
||||
"""Parses layer data.
|
||||
|
||||
Will parse CSV, base64, gzip-base64, or zlip-base64 encoded data.
|
||||
@@ -145,65 +148,98 @@ def _parse_data(element: etree.Element, layer_width: int) -> objects.LayerData:
|
||||
|
||||
|
||||
def _parse_layer(
|
||||
element: etree.Element, layer_type: objects.LayerType
|
||||
) -> objects.Layer:
|
||||
"""Parse layer element given."""
|
||||
width = int(element.attrib["width"])
|
||||
height = int(element.attrib["height"])
|
||||
size = objects.OrderedPair(width, height)
|
||||
data_element = element.find("./data")
|
||||
if data_element is not None:
|
||||
data: objects.LayerData = _parse_data(data_element, width)
|
||||
else:
|
||||
raise ValueError("{element} has no child data element.")
|
||||
layer_element: etree.Element
|
||||
) -> Tuple[
|
||||
int,
|
||||
str,
|
||||
Optional[objects.OrderedPair],
|
||||
Optional[float],
|
||||
Optional[objects.Properties],
|
||||
]:
|
||||
"""Parses all of the attributes for a Layer object.
|
||||
|
||||
return objects.Layer(size, data, **layer_type.__dict__)
|
||||
Args:
|
||||
layer_element: The layer element to be parsed.
|
||||
|
||||
Returns:
|
||||
|
||||
def _parse_layer_type(layer_element: etree.Element) -> objects.LayerType:
|
||||
"""Parse layer type element given."""
|
||||
"""
|
||||
id = int(layer_element.attrib["id"])
|
||||
|
||||
name = layer_element.attrib["name"]
|
||||
|
||||
layer_type_object = objects.LayerType(id, name)
|
||||
offset: Optional[objects.OrderedPair]
|
||||
offset_x_attrib = layer_element.attrib.get("offsetx")
|
||||
offset_y_attrib = layer_element.attrib.get("offsety")
|
||||
# If any offset is present, we need to return an OrderedPair
|
||||
# Unknown if one of the offsets could be absent.
|
||||
if any([offset_x_attrib, offset_y_attrib]):
|
||||
if offset_x_attrib:
|
||||
offset_x = float(offset_x_attrib)
|
||||
else:
|
||||
offset_x = 0.0
|
||||
if offset_y_attrib:
|
||||
offset_y = float(offset_y_attrib)
|
||||
else:
|
||||
offset_y = 0.0
|
||||
|
||||
try:
|
||||
offset_x = float(layer_element.attrib["offsetx"])
|
||||
except KeyError:
|
||||
offset_x = 0
|
||||
offset = objects.OrderedPair(offset_x, offset_y)
|
||||
else:
|
||||
offset = None
|
||||
|
||||
try:
|
||||
offset_y = float(layer_element.attrib["offsety"])
|
||||
except KeyError:
|
||||
offset_y = 0
|
||||
offset = objects.OrderedPair(offset_x, offset_y)
|
||||
|
||||
try:
|
||||
layer_type_object.opacity = round(
|
||||
float(layer_element.attrib["opacity"]) * 255
|
||||
)
|
||||
except KeyError:
|
||||
pass
|
||||
opacity: Optional[float]
|
||||
opacity_attrib = layer_element.attrib.get("opacity")
|
||||
if opacity_attrib:
|
||||
opacity = float(opacity_attrib)
|
||||
else:
|
||||
opacity = None
|
||||
|
||||
properties: Optional[objects.Properties]
|
||||
properties_element = layer_element.find("./properties")
|
||||
if properties_element is not None:
|
||||
layer_type_object.properties = _parse_properties_element(
|
||||
properties_element
|
||||
)
|
||||
properties = _parse_properties_element(properties_element)
|
||||
else:
|
||||
properties = None
|
||||
|
||||
if layer_element.tag == "layer":
|
||||
return _parse_layer(layer_element, layer_type_object)
|
||||
elif layer_element.tag == "objectgroup":
|
||||
return _parse_object_group(layer_element, layer_type_object)
|
||||
# else:
|
||||
# return _parse_layer_group(layer_element, layer_type_object)
|
||||
return id, name, offset, opacity, properties
|
||||
|
||||
|
||||
def _parse_tile_layer(element: etree.Element,) -> objects.TileLayer:
|
||||
"""Parses tile layer element.
|
||||
|
||||
Args:
|
||||
element: The layer element to be parsed.
|
||||
|
||||
Returns:
|
||||
TileLayer: The tile layer object.
|
||||
"""
|
||||
id, name, offset, opacity, properties = _parse_layer(element)
|
||||
|
||||
width = int(element.attrib["width"])
|
||||
height = int(element.attrib["height"])
|
||||
size = objects.Size(width, height)
|
||||
|
||||
data_element = element.find("./data")
|
||||
if data_element is not None:
|
||||
data: objects.LayerData = _parse_data(data_element, width)
|
||||
else:
|
||||
raise ValueError(f"{element} has no child data element.")
|
||||
|
||||
return objects.TileLayer(
|
||||
id, name, offset, opacity, properties, size, data
|
||||
)
|
||||
|
||||
|
||||
def _parse_objects(
|
||||
object_elements: List[etree.Element]
|
||||
) -> List[objects.TiledObject]:
|
||||
"""
|
||||
"""Parses objects found in the 'objectgroup' element.
|
||||
|
||||
Args:
|
||||
object_elements: List of object elements to be parsed.
|
||||
|
||||
Returns:
|
||||
list: List of parsed tiled objects.
|
||||
"""
|
||||
tiled_objects: List[objects.TiledObject] = []
|
||||
|
||||
@@ -213,7 +249,7 @@ def _parse_objects(
|
||||
location_y = float(object_element.attrib["y"])
|
||||
location = objects.OrderedPair(location_x, location_y)
|
||||
|
||||
object = objects.TiledObject(id, location)
|
||||
tiled_object = objects.TiledObject(id, location)
|
||||
|
||||
try:
|
||||
width = float(object_element.attrib["width"])
|
||||
@@ -225,45 +261,57 @@ def _parse_objects(
|
||||
except KeyError:
|
||||
height = 0
|
||||
|
||||
object.size = objects.Size(width, height)
|
||||
tiled_object.size = objects.Size(width, height)
|
||||
|
||||
try:
|
||||
object.opacity = round(
|
||||
float(object_element.attrib["opacity"]) * 255
|
||||
)
|
||||
tiled_object.opacity = float(object_element.attrib["opacity"])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
try:
|
||||
object.rotation = int(object_element.attrib["rotation"])
|
||||
tiled_object.rotation = int(object_element.attrib["rotation"])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
try:
|
||||
object.name = object_element.attrib["name"]
|
||||
tiled_object.name = object_element.attrib["name"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
try:
|
||||
tiled_object.type = object_element.attrib["type"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
properties_element = object_element.find("./properties")
|
||||
if properties_element is not None:
|
||||
object.properties = _parse_properties_element(properties_element)
|
||||
tiled_object.properties = _parse_properties_element(
|
||||
properties_element
|
||||
)
|
||||
|
||||
tiled_objects.append(object)
|
||||
tiled_objects.append(tiled_object)
|
||||
|
||||
return tiled_objects
|
||||
|
||||
|
||||
def _parse_object_group(
|
||||
element: etree.Element, layer_type: objects.LayerType
|
||||
) -> objects.ObjectGroup:
|
||||
def _parse_object_layer(element: etree.Element,) -> objects.ObjectLayer:
|
||||
"""Parse the objectgroup element given.
|
||||
|
||||
Args:
|
||||
layer_type (objects.LayerType):
|
||||
id: The id of the layer.
|
||||
name: The name of the layer.
|
||||
offset: The offset of the layer.
|
||||
opacity: The opacity of the layer.
|
||||
properties: The Properties object of the layer.
|
||||
|
||||
Returns:
|
||||
ObjectLayer: The object layer object.
|
||||
"""
|
||||
id, name, offset, opacity, properties = _parse_layer(element)
|
||||
|
||||
tiled_objects = _parse_objects(element.findall("./object"))
|
||||
|
||||
object_group = objects.ObjectGroup(tiled_objects, **layer_type.__dict__)
|
||||
try:
|
||||
color = utilities.parse_color(element.attrib["color"])
|
||||
except KeyError:
|
||||
@@ -274,7 +322,85 @@ def _parse_object_group(
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
return object_group
|
||||
return objects.ObjectLayer(
|
||||
id,
|
||||
name,
|
||||
offset,
|
||||
opacity,
|
||||
properties,
|
||||
tiled_objects,
|
||||
color,
|
||||
draw_order,
|
||||
)
|
||||
|
||||
|
||||
def _parse_layer_group(element: etree.Element,) -> objects.LayerGroup:
|
||||
"""Parse the objectgroup element given.
|
||||
|
||||
Args:
|
||||
layer_type (objects.LayerType):
|
||||
id: The id of the layer.
|
||||
name: The name of the layer.
|
||||
offset: The offset of the layer.
|
||||
opacity: The opacity of the layer.
|
||||
properties: The Properties object of the layer.
|
||||
|
||||
Returns:
|
||||
LayerGroup: The layer group object.
|
||||
"""
|
||||
id, name, offset, opacity, properties = _parse_layer(element)
|
||||
|
||||
layers = _get_layers(element)
|
||||
|
||||
return objects.LayerGroup(id, name, offset, opacity, properties, layers)
|
||||
|
||||
|
||||
def _get_layer_parser(
|
||||
layer_tag: str
|
||||
) -> Optional[Callable[[etree.Element], objects.Layer]]:
|
||||
"""Gets a the parser for the layer type specified.
|
||||
|
||||
Layer tags are 'layer' for a tile layer, 'objectgroup' for an object
|
||||
layer, and 'group' for a layer group. If anything else is passed,
|
||||
returns None.
|
||||
|
||||
Args:
|
||||
layer_tag: Specifies the layer type to be parsed based on the element
|
||||
tag.
|
||||
|
||||
Returns:
|
||||
Callable: the function to be used to parse the layer.
|
||||
None: The element is not a map layer.
|
||||
"""
|
||||
if layer_tag == "layer":
|
||||
return _parse_tile_layer
|
||||
elif layer_tag == "objectgroup":
|
||||
return _parse_object_layer
|
||||
elif layer_tag == "group":
|
||||
return _parse_layer_group
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def _get_layers(map_element: etree.Element) -> List[objects.Layer]:
|
||||
"""Parse layer type element given.
|
||||
|
||||
Retains draw order based on the returned lists index FIXME: confirm
|
||||
|
||||
Args:
|
||||
map_element: The element containing the layer.
|
||||
|
||||
Returns:
|
||||
List[Layer]: A list of the layers, ordered by draw order.
|
||||
FIXME: confirm
|
||||
"""
|
||||
layers: List[objects.Layer] = []
|
||||
for element in map_element.findall("./"):
|
||||
layer_parser = _get_layer_parser(element.tag)
|
||||
if layer_parser:
|
||||
layers.append(layer_parser(element))
|
||||
|
||||
return layers
|
||||
|
||||
|
||||
@functools.lru_cache()
|
||||
@@ -283,7 +409,7 @@ def _parse_external_tile_set(
|
||||
) -> objects.TileSet:
|
||||
"""Parses an external tile set.
|
||||
|
||||
Caches the results to speed up subsequent instances.
|
||||
Caches the results to speed up subsequent maps with identical tilesets.
|
||||
"""
|
||||
source = Path(tile_set_element.attrib["source"])
|
||||
tile_set_tree = etree.parse(str(parent_dir / Path(source))).getroot()
|
||||
@@ -292,6 +418,7 @@ def _parse_external_tile_set(
|
||||
|
||||
|
||||
def _parse_hitboxes(element: etree.Element) -> List[objects.TiledObject]:
|
||||
"""Parses all hitboxes for a given tile."""
|
||||
return _parse_objects(element.findall("./object"))
|
||||
|
||||
|
||||
@@ -442,7 +569,7 @@ def _parse_tile_set(tile_set_element: etree.Element) -> objects.TileSet:
|
||||
name = tile_set_element.attrib["name"]
|
||||
max_tile_width = int(tile_set_element.attrib["tilewidth"])
|
||||
max_tile_height = int(tile_set_element.attrib["tileheight"])
|
||||
max_tile_size = objects.OrderedPair(max_tile_width, max_tile_height)
|
||||
max_tile_size = objects.Size(max_tile_width, max_tile_height)
|
||||
|
||||
spacing = None
|
||||
try:
|
||||
@@ -521,7 +648,7 @@ def _parse_tile_set(tile_set_element: etree.Element) -> objects.TileSet:
|
||||
)
|
||||
|
||||
|
||||
def parse_tile_map(tmx_file: Union[str, Path]):
|
||||
def parse_tile_map(tmx_file: Union[str, Path]) -> objects.TileMap:
|
||||
# setting up XML parsing
|
||||
map_tree = etree.parse(str(tmx_file))
|
||||
map_element = map_tree.getroot()
|
||||
@@ -535,10 +662,10 @@ def parse_tile_map(tmx_file: Union[str, Path]):
|
||||
render_order = map_element.attrib["renderorder"]
|
||||
map_width = int(map_element.attrib["width"])
|
||||
map_height = int(map_element.attrib["height"])
|
||||
map_size = objects.OrderedPair(map_width, map_height)
|
||||
map_size = objects.Size(map_width, map_height)
|
||||
tile_width = int(map_element.attrib["tilewidth"])
|
||||
tile_height = int(map_element.attrib["tileheight"])
|
||||
tile_size = objects.OrderedPair(tile_width, tile_height)
|
||||
tile_size = objects.Size(tile_width, tile_height)
|
||||
|
||||
infinite_attribute = map_element.attrib["infinite"]
|
||||
infinite = True if infinite_attribute == "true" else False
|
||||
@@ -571,14 +698,7 @@ def parse_tile_map(tmx_file: Union[str, Path]):
|
||||
parent_dir, tile_set_element
|
||||
)
|
||||
|
||||
# parse all layers
|
||||
layers: List[objects.LayerType] = []
|
||||
layer_tags = ["layer", "objectgroup", "group"]
|
||||
for element in map_element.findall("./"):
|
||||
if element.tag not in layer_tags:
|
||||
# only layer_tags are layer elements
|
||||
continue
|
||||
layers.append(_parse_layer_type(element))
|
||||
layers = _get_layers(map_element)
|
||||
|
||||
tile_map = objects.TileMap(
|
||||
parent_dir,
|
||||
58
setup.py
58
setup.py
@@ -3,38 +3,36 @@ import sys
|
||||
from setuptools import setup
|
||||
|
||||
BUILD = 0
|
||||
VERSION = '0.1'
|
||||
VERSION = "0.0.1"
|
||||
RELEASE = VERSION
|
||||
|
||||
if __name__ == '__main__':
|
||||
readme = path.join(path.dirname(path.abspath(__file__)), 'README.md')
|
||||
with open(readme, 'r') as f:
|
||||
if __name__ == "__main__":
|
||||
readme = path.join(path.dirname(path.abspath(__file__)), "README.md")
|
||||
with open(readme, "r") as f:
|
||||
long_desc = f.read()
|
||||
|
||||
setup(
|
||||
name='pytiled_parser',
|
||||
version=RELEASE,
|
||||
description='Python Library for parsing Tiled Map Editor maps.',
|
||||
long_description=long_desc,
|
||||
author='Benjamin Kirkbride',
|
||||
author_email='BenjaminKirkbride@gmail.com',
|
||||
license='MIT',
|
||||
url='https://github.com/Beefy-Swain/pytiled_parser',
|
||||
download_url='https://github.com/Beefy-Swain/pytiled_parser',
|
||||
install_requires=[
|
||||
'dataclasses',
|
||||
],
|
||||
packages=['pytiled_parser'],
|
||||
classifiers=[
|
||||
'Development Status :: 1 - Planning',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: Implementation :: CPython',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
],
|
||||
test_suite='tests',
|
||||
)
|
||||
name="pytiled_parser",
|
||||
version=RELEASE,
|
||||
description="Python Library for parsing Tiled Map Editor maps.",
|
||||
long_description=long_desc,
|
||||
author="Benjamin Kirkbride",
|
||||
author_email="BenjaminKirkbride@gmail.com",
|
||||
license="MIT",
|
||||
url="https://github.com/Beefy-Swain/pytiled_parser",
|
||||
download_url="https://github.com/Beefy-Swain/pytiled_parser",
|
||||
install_requires=["dataclasses"],
|
||||
packages=["pytiled_parser"],
|
||||
classifiers=[
|
||||
"Development Status :: 1 - Planning",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
],
|
||||
test_suite="tests",
|
||||
)
|
||||
|
||||
197
test/output.py
197
test/output.py
@@ -1,177 +1,20 @@
|
||||
{
|
||||
"background_color": None,
|
||||
"hex_side_length": None,
|
||||
"infinite": False,
|
||||
"layers": [
|
||||
Layer(
|
||||
size=OrderedPair(x=8, y=6),
|
||||
data=[
|
||||
[1, 2, 3, 4, 5, 6, 7, 8],
|
||||
[9, 10, 11, 12, 13, 14, 15, 16],
|
||||
[17, 18, 19, 20, 21, 22, 23, 24],
|
||||
[25, 26, 27, 28, 29, 30, 31, 32],
|
||||
[33, 34, 35, 36, 37, 38, 39, 40],
|
||||
[41, 42, 43, 44, 45, 46, 47, 48],
|
||||
],
|
||||
id=1,
|
||||
name="Tile Layer 1",
|
||||
offset=OrderedPair(x=0, y=0),
|
||||
opacity=255,
|
||||
properties=None,
|
||||
)
|
||||
],
|
||||
"map_size": OrderedPair(x=8, y=6),
|
||||
"next_layer_id": 2,
|
||||
"next_object_id": 1,
|
||||
"orientation": "orthogonal",
|
||||
"parent_dir": PosixPath(
|
||||
"/home/benk/Projects/pytiled_parser/venv/pytiled_parser/tests/test_data"
|
||||
),
|
||||
"properties": {
|
||||
"bool property - false": False,
|
||||
"bool property - true": True,
|
||||
"color property": Color(red=73, green=252, blue=255, alpha=255),
|
||||
"file property": PosixPath("../../../../../../../../var/log/syslog"),
|
||||
"float property": 1.23456789,
|
||||
"int property": 13,
|
||||
"string property": "Hello, World!!",
|
||||
},
|
||||
"render_order": "right-down",
|
||||
"stagger_axis": None,
|
||||
"stagger_index": None,
|
||||
"tile_sets": {
|
||||
1: TileSet(
|
||||
name="tile_set_image",
|
||||
max_tile_size=OrderedPair(x=32, y=32),
|
||||
spacing=1,
|
||||
margin=1,
|
||||
tile_count=48,
|
||||
columns=8,
|
||||
tile_offset=None,
|
||||
grid=None,
|
||||
properties=None,
|
||||
image=Image(
|
||||
source="images/tmw_desert_spacing.png",
|
||||
size=Size(width=265, height=199),
|
||||
trans=None,
|
||||
),
|
||||
terrain_types=None,
|
||||
tiles={
|
||||
9: Tile(
|
||||
id=9,
|
||||
type=None,
|
||||
terrain=None,
|
||||
animation=None,
|
||||
image=None,
|
||||
hitboxes=[
|
||||
Object(
|
||||
id=2,
|
||||
location=OrderedPair(x=1.0, y=1.0),
|
||||
size=Size(width=32.0, height=32.0),
|
||||
rotation=1,
|
||||
opacity=255,
|
||||
name="wall",
|
||||
type=None,
|
||||
properties=None,
|
||||
template=None,
|
||||
)
|
||||
],
|
||||
),
|
||||
19: Tile(
|
||||
id=19,
|
||||
type=None,
|
||||
terrain=None,
|
||||
animation=None,
|
||||
image=None,
|
||||
hitboxes=[
|
||||
Object(
|
||||
id=1,
|
||||
location=OrderedPair(x=32.0, y=1.0),
|
||||
size=Size(width=0, height=0),
|
||||
rotation=1,
|
||||
opacity=255,
|
||||
name="wall corner",
|
||||
type=None,
|
||||
properties=None,
|
||||
template=None,
|
||||
)
|
||||
],
|
||||
),
|
||||
20: Tile(
|
||||
id=20,
|
||||
type=None,
|
||||
terrain=None,
|
||||
animation=None,
|
||||
image=None,
|
||||
hitboxes=[
|
||||
Object(
|
||||
id=1,
|
||||
location=OrderedPair(x=1.45455, y=1.45455),
|
||||
size=Size(width=0, height=0),
|
||||
rotation=1,
|
||||
opacity=255,
|
||||
name="polyline",
|
||||
type=None,
|
||||
properties=None,
|
||||
template=None,
|
||||
)
|
||||
],
|
||||
),
|
||||
31: Tile(
|
||||
id=31,
|
||||
type=None,
|
||||
terrain=None,
|
||||
animation=None,
|
||||
image=None,
|
||||
hitboxes=[
|
||||
Object(
|
||||
id=1,
|
||||
location=OrderedPair(x=5.09091, y=2.54545),
|
||||
size=Size(width=19.6364, height=19.2727),
|
||||
rotation=1,
|
||||
opacity=255,
|
||||
name="rock 1",
|
||||
type=None,
|
||||
properties=None,
|
||||
template=None,
|
||||
),
|
||||
Object(
|
||||
id=2,
|
||||
location=OrderedPair(x=16.1818, y=22.0),
|
||||
size=Size(width=8.54545, height=8.36364),
|
||||
rotation=-1,
|
||||
opacity=255,
|
||||
name="rock 2",
|
||||
type=None,
|
||||
properties=None,
|
||||
template=None,
|
||||
),
|
||||
],
|
||||
),
|
||||
45: Tile(
|
||||
id=45,
|
||||
type=None,
|
||||
terrain=None,
|
||||
animation=None,
|
||||
image=None,
|
||||
hitboxes=[
|
||||
Object(
|
||||
id=1,
|
||||
location=OrderedPair(x=14.7273, y=26.3636),
|
||||
size=Size(width=0, height=0),
|
||||
rotation=0,
|
||||
opacity=255,
|
||||
name="sign",
|
||||
type=None,
|
||||
properties=None,
|
||||
template=None,
|
||||
)
|
||||
],
|
||||
),
|
||||
},
|
||||
)
|
||||
},
|
||||
"tile_size": OrderedPair(x=32, y=32),
|
||||
"tiled_version": "1.2.3",
|
||||
"version": "1.2",
|
||||
}
|
||||
{ 'background_color': None,
|
||||
'hex_side_length': None,
|
||||
'infinite': False,
|
||||
'layers': [ TileLayer(id=1, name='Tile Layer 1', offset=None, opacity=None, properties=None, size=Size(width=10, height=10), data=[[1, 2, 3, 4, 5, 6, 7, 8, 30, 30], [9, 10, 11, 12, 13, 14, 15, 16, 30, 30], [17, 18, 19, 20, 21, 22, 23, 24, 30, 30], [25, 26, 27, 28, 29, 30, 31, 32, 30, 30], [33, 34, 35, 36, 37, 38, 39, 40, 30, 30], [41, 42, 43, 44, 45, 46, 47, 48, 30, 30], [30, 30, 30, 30, 30, 30, 30, 30, 30, 30], [30, 30, 30, 30, 30, 30, 30, 30, 30, 30], [30, 30, 30, 30, 30, 30, 30, 30, 30, 30], [30, 30, 30, 30, 30, 30, 30, 30, 30, 30]]),
|
||||
TileLayer(id=2, name='Tile Layer 2', offset=None, opacity=0.5, properties=None, size=Size(width=10, height=10), data=[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 46, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 6, 7, 7, 7, 7, 7, 8, 0], [0, 0, 14, 15, 15, 15, 15, 15, 16, 0], [0, 0, 22, 23, 23, 23, 23, 23, 24, 0]]),
|
||||
LayerGroup(id=3, name='Group 1', offset=None, opacity=None, properties={'bool property': True}, layers=[TileLayer(id=5, name='Tile Layer 4', offset=OrderedPair(x=49.0, y=-50.0), opacity=None, properties=None, size=Size(width=10, height=10), data=[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 31, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]), TileLayer(id=4, name='Tile Layer 3', offset=None, opacity=None, properties=None, size=Size(width=10, height=10), data=[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 2, 3, 0, 0, 0, 0, 0, 0, 0], [9, 10, 11, 0, 0, 0, 0, 0, 0, 0], [17, 18, 19, 0, 0, 0, 0, 0, 0, 0]])]),
|
||||
ObjectLayer(id=6, name='Object Layer 1', offset=OrderedPair(x=4.66667, y=-4.33333), opacity=0.9, properties=None, tiled_objects=[TiledObject(id=1, location=OrderedPair(x=200.25, y=210.75), size=Size(width=47.25, height=25.0), rotation=15, opacity=1, name='rectangle 1', type='rectangle type', properties=None, template=None), TiledObject(id=2, location=OrderedPair(x=252.5, y=87.75), size=Size(width=0, height=0), rotation=-21, opacity=1, name='polygon 1', type='polygon type', properties=None, template=None), TiledObject(id=3, location=OrderedPair(x=198.75, y=102.5), size=Size(width=17.75, height=14.25), rotation=0, opacity=1, name='elipse 1', type='elipse type', properties=None, template=None), TiledObject(id=4, location=OrderedPair(x=174.25, y=186.0), size=Size(width=0, height=0), rotation=0, opacity=1, name='point 1', type='point type', properties=None, template=None), TiledObject(id=7, location=OrderedPair(x=11.3958, y=48.5833), size=Size(width=107.625, height=27.25), rotation=0, opacity=1, name='insert text 1', type='insert text type', properties=None, template=None), TiledObject(id=6, location=OrderedPair(x=47.25, y=72.5), size=Size(width=47.0, height=53.0), rotation=31, opacity=1, name='inserted tile 1', type='inserted tile type', properties={'tile property bool': True}, template=None), TiledObject(id=8, location=OrderedPair(x=144.667, y=112.0), size=Size(width=0, height=0), rotation=0, opacity=1, name='polyline 1', type='polyline type', properties=None, template=None), TiledObject(id=9, location=OrderedPair(x=69.8333, y=168.333), size=Size(width=0, height=0), rotation=0, opacity=1, name='polygon 2', type='polygon type', properties=None, template=None)], color=Color(red=0, green=0, blue=0, alpha=255), draw_order='index')],
|
||||
'map_size': Size(width=10, height=10),
|
||||
'next_layer_id': 16,
|
||||
'next_object_id': 10,
|
||||
'orientation': 'orthogonal',
|
||||
'parent_dir': PosixPath('/home/benk/Projects/pytiled_parser/venv/pytiled_parser/tests/test_data'),
|
||||
'properties': None,
|
||||
'render_order': 'right-down',
|
||||
'stagger_axis': None,
|
||||
'stagger_index': None,
|
||||
'tile_sets': { 1: TileSet(name='tile_set_image', max_tile_size=Size(width=32, height=32), spacing=1, margin=1, tile_count=48, columns=8, tile_offset=None, grid=None, properties=None, image=Image(source='images/tmw_desert_spacing.png', size=Size(width=265, height=199), trans=None), terrain_types=None, tiles={})},
|
||||
'tile_size': Size(width=32, height=32),
|
||||
'tiled_version': '1.2.3',
|
||||
'version': '1.2'}
|
||||
|
||||
@@ -10,7 +10,7 @@ pp = pprint.PrettyPrinter(indent=4, compact=True, width=100)
|
||||
|
||||
pp = pp.pprint
|
||||
|
||||
MAP_NAME = '/home/benk/Projects/pytiled_parser/venv/pytiled_parser/tests/test_data/test_map_simple_hitboxes.tmx'
|
||||
MAP_NAME = "/home/benk/Projects/pytiled_parser/venv/pytiled_parser/tests/test_data/test_map_image_tile_set.tmx"
|
||||
|
||||
map = pytiled_parser.parse_tile_map(MAP_NAME)
|
||||
|
||||
|
||||
18
tests/unit2/test_parser.py
Normal file
18
tests/unit2/test_parser.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import pytest
|
||||
|
||||
import xml.etree.ElementTree as etree
|
||||
|
||||
from typing import Callable
|
||||
|
||||
from pytiled_parser import xml_parser
|
||||
|
||||
|
||||
def _get_root_element(xml: str) -> Callable:
|
||||
pass
|
||||
|
||||
|
||||
layer_data = []
|
||||
|
||||
|
||||
def test_parse_layer(element, expected):
|
||||
pass
|
||||
@@ -16,7 +16,8 @@ def test_map_simple():
|
||||
TMX with a very simple spritesheet tile set and some properties.
|
||||
"""
|
||||
map = pytiled_parser.parse_tile_map(
|
||||
Path("../test_data/test_map_simple.tmx"))
|
||||
Path("../test_data/test_map_simple.tmx")
|
||||
)
|
||||
|
||||
# map
|
||||
# unsure how to get paths to compare propperly
|
||||
@@ -38,13 +39,13 @@ def test_map_simple():
|
||||
assert map.background_color == None
|
||||
|
||||
assert map.properties == {
|
||||
"bool property - false": False,
|
||||
"bool property - true": True,
|
||||
"color property": (0x49, 0xfc, 0xff, 0xff),
|
||||
"file property": Path("/var/log/syslog"),
|
||||
"float property": 1.23456789,
|
||||
"int property": 13,
|
||||
"string property": "Hello, World!!"
|
||||
"bool property - false": False,
|
||||
"bool property - true": True,
|
||||
"color property": (0x49, 0xFC, 0xFF, 0xFF),
|
||||
"file property": Path("/var/log/syslog"),
|
||||
"float property": 1.23456789,
|
||||
"int property": 13,
|
||||
"string property": "Hello, World!!",
|
||||
}
|
||||
|
||||
# tileset
|
||||
@@ -60,7 +61,8 @@ def test_map_simple():
|
||||
|
||||
# unsure how to get paths to compare propperly
|
||||
assert str(map.tile_sets[1].image.source) == (
|
||||
"images/tmw_desert_spacing.png")
|
||||
"images/tmw_desert_spacing.png"
|
||||
)
|
||||
assert map.tile_sets[1].image.trans == None
|
||||
assert map.tile_sets[1].image.size == (265, 199)
|
||||
|
||||
@@ -68,28 +70,31 @@ def test_map_simple():
|
||||
assert map.tile_sets[1].tiles == {}
|
||||
|
||||
# layers
|
||||
assert map.layers[0].data == [[1,2,3,4,5,6,7,8],
|
||||
[9,10,11,12,13,14,15,16],
|
||||
[17,18,19,20,21,22,23,24],
|
||||
[25,26,27,28,29,30,31,32],
|
||||
[33,34,35,36,37,38,39,40],
|
||||
[41,42,43,44,45,46,47,48]]
|
||||
assert map.layers[0].data == [
|
||||
[1, 2, 3, 4, 5, 6, 7, 8],
|
||||
[9, 10, 11, 12, 13, 14, 15, 16],
|
||||
[17, 18, 19, 20, 21, 22, 23, 24],
|
||||
[25, 26, 27, 28, 29, 30, 31, 32],
|
||||
[33, 34, 35, 36, 37, 38, 39, 40],
|
||||
[41, 42, 43, 44, 45, 46, 47, 48],
|
||||
]
|
||||
assert map.layers[0].id == 1
|
||||
assert map.layers[0].name == "Tile Layer 1"
|
||||
assert map.layers[0].offset == (0, 0)
|
||||
assert map.layers[0].opacity == 0xFF
|
||||
assert map.layers[0].offset == None
|
||||
assert map.layers[0].opacity == None
|
||||
assert map.layers[0].properties == None
|
||||
assert map.layers[0].size == (8, 6)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"test_input,expected", [
|
||||
("#001122", (0x00, 0x11, 0x22, 0xff)),
|
||||
("001122", (0x00, 0x11, 0x22, 0xff)),
|
||||
("#FF001122", (0x00, 0x11, 0x22, 0xff)),
|
||||
("FF001122", (0x00, 0x11, 0x22, 0xff)),
|
||||
("FF001122", (0x00, 0x11, 0x22, 0xff)),
|
||||
]
|
||||
"test_input,expected",
|
||||
[
|
||||
("#001122", (0x00, 0x11, 0x22, 0xFF)),
|
||||
("001122", (0x00, 0x11, 0x22, 0xFF)),
|
||||
("#FF001122", (0x00, 0x11, 0x22, 0xFF)),
|
||||
("FF001122", (0x00, 0x11, 0x22, 0xFF)),
|
||||
("FF001122", (0x00, 0x11, 0x22, 0xFF)),
|
||||
],
|
||||
)
|
||||
def test_color_parsing(test_input, expected):
|
||||
"""
|
||||
Reference in New Issue
Block a user