From 69363f22fa14b7c7bab6f4e56fb8d7d65137c240 Mon Sep 17 00:00:00 2001 From: Benjamin Kirkbride Date: Wed, 8 May 2019 15:21:10 -0400 Subject: [PATCH] yay --- pytiled_parser/__init__.py | 2 +- pytiled_parser/objects.py | 147 ++++------ pytiled_parser/{parser.py => xml_parser.py} | 274 +++++++++++++----- setup.py | 58 ++-- test/output.py | 197 ++----------- test/test_tiled.py | 2 +- tests/unit2/test_parser.py | 18 ++ ...=> test_pytiled_parser_integration.py.bax} | 53 ++-- 8 files changed, 350 insertions(+), 401 deletions(-) rename pytiled_parser/{parser.py => xml_parser.py} (73%) create mode 100644 tests/unit2/test_parser.py rename tests/unit2/{test_pytiled_parser.py => test_pytiled_parser_integration.py.bax} (66%) diff --git a/pytiled_parser/__init__.py b/pytiled_parser/__init__.py index 5ec4574..bcfa056 100644 --- a/pytiled_parser/__init__.py +++ b/pytiled_parser/__init__.py @@ -1,4 +1,4 @@ from . import utilities from . import objects -from .parser import parse_tile_map +from .xml_parser import parse_tile_map diff --git a/pytiled_parser/objects.py b/pytiled_parser/objects.py index f53c617..49a15e6 100644 --- a/pytiled_parser/objects.py +++ b/pytiled_parser/objects.py @@ -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 diff --git a/pytiled_parser/parser.py b/pytiled_parser/xml_parser.py similarity index 73% rename from pytiled_parser/parser.py rename to pytiled_parser/xml_parser.py index 356eef4..4f7c19d 100644 --- a/pytiled_parser/parser.py +++ b/pytiled_parser/xml_parser.py @@ -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, diff --git a/setup.py b/setup.py index edb257d..d8b13db 100644 --- a/setup.py +++ b/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", + ) diff --git a/test/output.py b/test/output.py index 4faf5ab..7190a30 100644 --- a/test/output.py +++ b/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'} diff --git a/test/test_tiled.py b/test/test_tiled.py index 3a6dde2..235d112 100644 --- a/test/test_tiled.py +++ b/test/test_tiled.py @@ -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) diff --git a/tests/unit2/test_parser.py b/tests/unit2/test_parser.py new file mode 100644 index 0000000..a03f8bd --- /dev/null +++ b/tests/unit2/test_parser.py @@ -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 diff --git a/tests/unit2/test_pytiled_parser.py b/tests/unit2/test_pytiled_parser_integration.py.bax similarity index 66% rename from tests/unit2/test_pytiled_parser.py rename to tests/unit2/test_pytiled_parser_integration.py.bax index d341f42..904548a 100644 --- a/tests/unit2/test_pytiled_parser.py +++ b/tests/unit2/test_pytiled_parser_integration.py.bax @@ -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): """