This commit is contained in:
Benjamin Kirkbride
2019-05-08 15:21:10 -04:00
parent e8809745be
commit 69363f22fa
8 changed files with 350 additions and 401 deletions

View File

@@ -1,4 +1,4 @@
from . import utilities
from . import objects
from .parser import parse_tile_map
from .xml_parser import parse_tile_map

View File

@@ -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

View File

@@ -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,

View File

@@ -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",
)

View File

@@ -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'}

View File

@@ -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)

View 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

View File

@@ -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):
"""