Files
pytiled_parser/pytiled_parser/tiled_object.py

435 lines
12 KiB
Python

# pylint: disable=too-few-public-methods
from typing import Callable, Dict, List, Optional, Union
import attr
from typing_extensions import TypedDict
from . import properties as properties_
from .common_types import Color, OrderedPair, Size
@attr.s(auto_attribs=True, kw_only=True)
class TiledObject:
"""TiledObject object.
See:
https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#object
Attributes:
id_: Unique ID of the tiled object. Each tiled object that is placed on a map
gets a unique id. Even if an tiled object was deleted, no tiled object gets
the same ID.
gid: Global tiled object ID.
coordinates: The location of the tiled object in pixels.
size: The width of the tiled object in pixels (default: (0, 0)).
rotation: The rotation of the tiled object in degrees clockwise (default: 0).
opacity: The opacity of the tiled object. (default: 1)
name: The name of the tiled object.
type: The type of the tiled object.
properties: The properties of the TiledObject.
"""
id: int
coordinates: OrderedPair
size: Size = Size(0, 0)
rotation: float = 0
opacity: float = 1
visible: bool
name: Optional[str] = None
type: Optional[str] = None
properties: properties_.Properties = {}
@attr.s()
class Ellipse(TiledObject):
"""Elipse shape defined by a point, width, height, and rotation.
See: https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#ellipse
"""
@attr.s()
class Point(TiledObject):
"""Point defined by a coordinate (x,y).
See: https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#point
"""
@attr.s(auto_attribs=True, kw_only=True)
class Polygon(TiledObject):
"""Polygon shape defined by a set of connections between points.
See: https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#polygon
Attributes:
points: FIXME
"""
points: List[OrderedPair]
@attr.s(auto_attribs=True, kw_only=True)
class Polyline(TiledObject):
"""Polyline defined by a set of connections between points.
See:
https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#polyline
Attributes:
points: List of coordinates relative to the location of the object.
"""
points: List[OrderedPair]
@attr.s()
class Rectangle(TiledObject):
"""Rectangle shape defined by a point, width, and height.
See: https://doc.mapeditor.org/en/stable/manual/objects/#insert-rectangle
(objects in tiled are rectangles by default, so there is no specific
documentation on the tmx-map-format page for it.)
"""
@attr.s(auto_attribs=True, kw_only=True)
class Text(TiledObject):
"""Text object with associated settings.
See: https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#text
and https://doc.mapeditor.org/en/stable/manual/objects/#insert-text
Attributes:
font_family: The font family used (default: "sans-serif")
font_size: The size of the font in pixels. (default: 16)
wrap: Whether word wrapping is enabled. (default: False)
color: Color of the text. (default: #000000)
bold: Whether the font is bold. (default: False)
italic: Whether the font is italic. (default: False)
underline: Whether the text is underlined. (default: False)
strike_out: Whether the text is striked-out. (default: False)
kerning: Whether kerning should be used while rendering the text. (default:
False)
horizontal_align: Horizontal alignment of the text (default: "left")
vertical_align: Vertical alignment of the text (defalt: "top")
"""
text: str
color: Color = "#000000"
font_family: str = "sans-serif"
font_size: float = 16
bold: bool = False
italic: bool = False
kerning: bool = True
strike_out: bool = False
underline: bool = False
horizontal_align: str = "left"
vertical_align: str = "top"
wrap: bool = False
@attr.s(auto_attribs=True, kw_only=True)
class Tile(TiledObject):
"""Tile object
See: https://doc.mapeditor.org/en/stable/manual/objects/#insert-tile
Attributes:
gid: Reference to a global tile id.
"""
gid: int
class RawTextDict(TypedDict):
""" The keys and their types that appear in a Text JSON Object."""
text: str
color: Color
fontfamily: str
pixelsize: float # this is `font_size` in Text
bold: bool
italic: bool
strikeout: bool
underline: bool
kerning: bool
halign: str
valign: str
wrap: bool
class RawTiledObject(TypedDict):
""" The keys and their types that appear in a Tiled JSON Object."""
id: int
gid: int
x: float
y: float
width: float
height: float
rotation: float
opacity: float
visible: bool
name: str
type: str
properties: List[properties_.RawProperty]
ellipse: bool
point: bool
polygon: List[Dict[str, float]]
polyline: List[Dict[str, float]]
text: Dict[str, Union[float, str]]
RawTiledObjects = List[RawTiledObject]
def _get_common_attributes(raw_tiled_object: RawTiledObject) -> TiledObject:
""" Create a TiledObject containing all the attributes common to all tiled objects
Args:
raw_tiled_object: Raw Tiled object get common attributes from
Returns:
TiledObject: The attributes in common of all Tiled Objects
"""
# required attributes
id_ = raw_tiled_object["id"]
coordinates = OrderedPair(x=raw_tiled_object["x"], y=raw_tiled_object["y"])
visible = raw_tiled_object["visible"]
common_attributes = TiledObject(
id=id_, coordinates=coordinates, visible=visible, properties={}
)
# optional attributes
if any([raw_tiled_object.get("width"), raw_tiled_object.get("height")]):
# we have to check if either are truthy before we proceed to create Size
_width = raw_tiled_object.get("width")
if _width:
width = _width
else:
width = 0
_height = raw_tiled_object.get("height")
if _height:
height = _height
else:
height = 0
common_attributes.size = Size(width, height)
if raw_tiled_object.get("rotation") is not None:
common_attributes.rotation = raw_tiled_object["rotation"]
if raw_tiled_object.get("opacity") is not None:
common_attributes.opacity = raw_tiled_object["opacity"]
if raw_tiled_object.get("name") is not None:
common_attributes.name = raw_tiled_object["name"]
if raw_tiled_object.get("type") is not None:
common_attributes.type = raw_tiled_object["type"]
if raw_tiled_object.get("properties") is not None:
common_attributes.properties = properties_.cast(raw_tiled_object["properties"])
return common_attributes
def _cast_ellipse(raw_tiled_object: RawTiledObject) -> Ellipse:
""" Cast the raw_tiled_object to an Ellipse object.
Args:
raw_tiled_object: Raw Tiled object to be casted to an Ellipse
Returns:
Ellipse: The Ellipse object created from the raw_tiled_object
"""
return Ellipse(**_get_common_attributes(raw_tiled_object).__dict__)
def _cast_rectangle(raw_tiled_object: RawTiledObject) -> Rectangle:
""" Cast the raw_tiled_object to a Rectangle object.
Args:
raw_tiled_object: Raw Tiled object to be casted to a Rectangle
Returns:
Rectangle: The Rectangle object created from the raw_tiled_object
"""
return Rectangle(**_get_common_attributes(raw_tiled_object).__dict__)
def _cast_point(raw_tiled_object: RawTiledObject) -> Point:
""" Cast the raw_tiled_object to a Point object.
Args:
raw_tiled_object: Raw Tiled object to be casted to a Point
Returns:
Point: The Point object created from the raw_tiled_object
"""
return Point(**_get_common_attributes(raw_tiled_object).__dict__)
def _cast_tile(raw_tiled_object: RawTiledObject) -> Tile:
""" Cast the raw_tiled_object to a Tile object.
Args:
raw_tiled_object: Raw Tiled object to be casted to a Tile
Returns:
Tile: The Tile object created from the raw_tiled_object
"""
gid = raw_tiled_object["gid"]
return Tile(gid=gid, **_get_common_attributes(raw_tiled_object).__dict__)
def _cast_polygon(raw_tiled_object: RawTiledObject) -> Polygon:
""" Cast the raw_tiled_object to a Polygon object.
Args:
raw_tiled_object: Raw Tiled object to be casted to a Polygon
Returns:
Polygon: The Polygon object created from the raw_tiled_object
"""
polygon = []
for point in raw_tiled_object["polygon"]:
polygon.append(OrderedPair(point["x"], point["y"]))
return Polygon(points=polygon, **_get_common_attributes(raw_tiled_object).__dict__)
def _cast_polyline(raw_tiled_object: RawTiledObject) -> Polyline:
""" Cast the raw_tiled_object to a Polyline object.
Args:
raw_tiled_object: Raw Tiled Object to be casted to a Polyline
Returns:
Polyline: The Polyline object created from the raw_tiled_object
"""
polyline = []
for point in raw_tiled_object["polyline"]:
polyline.append(OrderedPair(point["x"], point["y"]))
return Polyline(
points=polyline, **_get_common_attributes(raw_tiled_object).__dict__
)
def _cast_text(raw_tiled_object: RawTiledObject) -> Text:
""" Cast the raw_tiled_object to a Text object.
Args:
raw_tiled_object: Raw Tiled object to be casted to a Text object
Returns:
Text: The Text object created from the raw_tiled_object
"""
# required attributes
raw_text_dict: RawTextDict = raw_tiled_object["text"]
text = raw_text_dict["text"]
# create base Text object
text_object = Text(text=text, **_get_common_attributes(raw_tiled_object).__dict__)
# optional attributes
if raw_text_dict.get("color") is not None:
text_object.color = raw_text_dict["color"]
if raw_text_dict.get("fontfamily") is not None:
text_object.font_family = raw_text_dict["fontfamily"]
if raw_text_dict.get("pixelsize") is not None:
text_object.font_size = raw_text_dict["pixelsize"]
if raw_text_dict.get("bold") is not None:
text_object.bold = raw_text_dict["bold"]
if raw_text_dict.get("italic") is not None:
text_object.italic = raw_text_dict["italic"]
if raw_text_dict.get("kerning") is not None:
text_object.kerning = raw_text_dict["kerning"]
if raw_text_dict.get("strikeout") is not None:
text_object.strike_out = raw_text_dict["strikeout"]
if raw_text_dict.get("underline") is not None:
text_object.underline = raw_text_dict["underline"]
if raw_text_dict.get("halign") is not None:
text_object.horizontal_align = raw_text_dict["halign"]
if raw_text_dict.get("valign") is not None:
text_object.vertical_align = raw_text_dict["valign"]
if raw_text_dict.get("wrap") is not None:
text_object.wrap = raw_text_dict["wrap"]
return text_object
def _get_caster(
raw_tiled_object: RawTiledObject,
) -> Callable[[RawTiledObject], TiledObject]:
""" Get the caster function for the raw tiled object.
Args:
raw_tiled_object: Raw Tiled object that is analysed to determine which caster
to return.
Returns:
Callable[[RawTiledObject], TiledObject]: The caster function.
"""
if raw_tiled_object.get("ellipse"):
return _cast_ellipse
if raw_tiled_object.get("point"):
return _cast_point
if raw_tiled_object.get("gid"):
# Only Tile objects have the `gid` key (I think)
return _cast_tile
if raw_tiled_object.get("polygon"):
return _cast_polygon
if raw_tiled_object.get("polyline"):
return _cast_polyline
if raw_tiled_object.get("text"):
return _cast_text
return _cast_rectangle
def cast(raw_tiled_object: RawTiledObject) -> TiledObject:
""" Cast the raw tiled object into a pytiled_parser type
Args:
raw_tiled_object: Raw Tiled object that is to be cast.
Returns:
TiledObject: a properly typed Tiled object.
"""
caster = _get_caster(raw_tiled_object)
tiled_object = caster(raw_tiled_object)
return tiled_object