"""Map objects, the parts of a map that aren't part of a tile grid
"""
from __future__ import division
from tmxlib import helpers, tile
NOT_GIVEN = object()
[docs]class MapObject(helpers.PixelPosMixin, helpers.LayerElementMixin):
"""A map object: something that's not placed on the fixed grid
Has several subclasses.
Can be either a "tile object", which has an associated tile much like a
map-tile, or a regular (non-tile) object that has a settable size.
init arguments, which become attributes:
.. attribute:: layer
The layer this object is on
.. attribute:: pixel_pos
The pixel coordinates
.. attribute:: pixel_size
Size of this object, as a (width, height) tuple, in pixels.
Only one of ``pixel_size`` and ``size`` may be specified.
.. attribute:: size
Size of this object, as a (width, height) tuple, in units of map
tiles.
.. attribute:: name
Name of the object. A string (or unicode)
.. attribute:: type
Type of the object. A string (or unicode). No semantics attached.
Other attributes:
.. attribute:: objtype
Type of the object: ``'rectangle'``, ``'tile'`` or ``'ellipse'``
.. attribute:: properties
Dict of string (or unicode) keys & values for custom data
.. attribute:: pos
Position of the object in tile coordinates, as a (x, y) float tuple
.. attribute:: map
The map associated with this object
Unpacked position attributes:
.. attribute:: x
.. attribute:: y
.. attribute:: pixel_x
.. attribute:: pixel_y
"""
def __init__(self, layer, pixel_pos, name=None, type=None):
self.layer = layer
self.pixel_pos = pixel_pos
self.name = name
self.type = type
self.properties = {}
@property
def pos(self):
return (self.pixel_pos[0] / self.layer.map.tile_width,
self.pixel_pos[1] / self.layer.map.tile_height - 1)
@pos.setter
def pos(self, value):
x, y = value
y += 1
self.pixel_pos = (x * self.layer.map.tile_width,
y * self.layer.map.tile_height)
[docs] def to_dict(self, y=None):
"""Export to a dict compatible with Tiled's JSON plugin"""
if y is None:
y = self.pixel_y
d = dict(
name=self.name or '',
type=self.type or '',
x=self.pixel_x, y=y,
visible=True,
properties=self.properties,
)
return d
@classmethod
[docs] def from_dict(cls, dct, layer):
"""Import from a dict compatible with Tiled's JSON plugin"""
if dct.get('ellipse', False):
return EllipseObject.from_dict(dct, layer)
elif dct.get('polygon', False):
return PolygonObject.from_dict(dct, layer)
elif dct.get('polyline', False):
return PolylineObject.from_dict(dct, layer)
else:
return RectangleObject.from_dict(dct, layer)
@classmethod
def _dict_helper(cls, dct, layer, **kwargs):
helpers.assert_item(dct, 'visible', True)
self = cls(
layer=layer,
pixel_pos=(dct.pop('x'), dct.pop('y')),
name=dct.pop('name', None),
type=dct.pop('type', None),
**kwargs
)
self.properties.update(dct.pop('properties', {}))
return self
class PointBasedObject(MapObject):
def __init__(self, layer, pixel_pos, size=None, pixel_size=None, name=None,
type=None, points=()):
MapObject.__init__(self, layer, pixel_pos, name, type)
self.points = list(points)
@helpers.from_dict_method
def from_dict(cls, dct, layer):
points = [(d['x'], d['y']) for d in dct.pop(cls.objtype)]
assert dct.pop('height', 0) == dct.pop('width', 0) == 0
return super(PointBasedObject, cls)._dict_helper(
dct, layer, points=points)
def to_dict(self, gid=None):
"""Export to a dict compatible with Tiled's JSON plugin"""
d = super(PointBasedObject, self).to_dict()
d['width'] = d['height'] = 0
d[self.objtype] = [{'x': x, 'y': y} for x, y in self.points]
return d
[docs]class PolygonObject(PointBasedObject):
"""A polygon object
See :class:`~tmxlib.mapobject.MapObject` for inherited members.
Extra init arguments, which become attributes:
.. attribute:: points
Size of this object, as a (width, height) tuple, in pixels.
Must be specified for non-tile objects, and must *not* be specified
for tile objects (unless the size matches the tile).
The format is list of iterables:
[(x0, y0), (x1, y1), ..., (xn, yn)]
"""
objtype = 'polygon'
[docs]class PolylineObject(PointBasedObject):
"""A polygon object
Behaves just like :class:`~tmxlib.mapobject.PolygonObject`, it's not
closed when drawn.
Has the same ``points`` attribute/argument as
:class:`~tmxlib.mapobject.PolygonObject`.
"""
objtype = 'polyline'
class SizedObject(helpers.TileMixin, MapObject):
def __init__(self, layer, pixel_pos, size=None, pixel_size=None, name=None,
type=None):
MapObject.__init__(self, layer, pixel_pos, name, type)
if pixel_size:
if size:
raise ValueError('Cannot specify both size and pixel_size')
self.pixel_size = pixel_size
elif size:
self.size = size
@property
def pixel_size(self):
return self._size
@pixel_size.setter
def pixel_size(self, value):
self._size = value
def to_dict(self, gid=None):
"""Export to a dict compatible with Tiled's JSON plugin"""
if gid:
y = self.pixel_y
else:
y = self.pixel_y - self.pixel_height
d = super(SizedObject, self).to_dict(y)
if gid:
pixel_width = pixel_height = 0
else:
pixel_width = self.pixel_width
pixel_height = self.pixel_height
d.update(
width=pixel_width,
height=pixel_height,
)
return d
@classmethod
def _dict_helper(cls, dct, layer, size, **kwargs):
return super(SizedObject, cls)._dict_helper(
dct,
layer,
pixel_size=size,
**kwargs
)
[docs]class RectangleObject(tile.TileLikeObject, SizedObject):
"""A rectangle object, either blank (sized) or a tile object
See :class:`MapObject` for inherited members.
Extra init arguments, which become attributes:
.. attribute:: pixel_size
Size of this object, as a (width, height) tuple, in pixels.
Must be specified for non-tile objects, and must *not* be specified
for tile objects (unless the size matches the tile).
Similar restrictions apply to setting the property (and ``width`` &
``height``).
.. attribute:: size
Size of this object, as a (width, height) tuple, in units of map
tiles.
Shares setting restrictions with ``pixel_size``.
Note that the constructor will nly accept one of ``size`` or
``pixel_size``, not both at the same time.
.. attribute:: value
Value of the tile, if it's a tile object.
See :class:`tmxlib.tile.TileLikeObject` for attributes and methods
shared with tiles.
"""
def __init__(self, layer, pixel_pos, size=None, pixel_size=None, name=None,
type=None, value=0):
tile.TileLikeObject.__init__(self)
self.layer = layer
self.value = value
SizedObject.__init__(
self, layer, pixel_pos, size, pixel_size, name, type)
def __nonzero__(self):
return True
__bool__ = __nonzero__
@property
def objtype(self):
if self.value:
return 'tile'
else:
return 'rectangle'
@property
def pixel_size(self):
if self.gid:
return super(RectangleObject, self).pixel_size
else:
return self._size
@pixel_size.setter
def pixel_size(self, value):
if self.gid:
if value != self.pixel_size:
raise TypeError("Cannot modify size of tile objects")
else:
self._size = value
@helpers.from_dict_method
[docs] def from_dict(cls, dct, layer):
gid = dct.pop('gid', 0)
if gid:
size = None
dct.pop('width')
dct.pop('height')
else:
size = dct.pop('width'), dct.pop('height')
dct['y'] = dct['y'] + size[1]
return super(RectangleObject, cls)._dict_helper(
dct, layer, size, value=gid)
[docs] def to_dict(self):
d = super(RectangleObject, self).to_dict(self.gid)
if self.value:
d['gid'] = self.value
return d
[docs]class EllipseObject(SizedObject):
"""An ellipse object
Extra init arguments, which become attributes:
.. attribute:: pixel_size
Size of this object, as a (width, height) tuple, in pixels.
Must be specified for non-tile objects, and must *not* be specified
for tile objects (unless the size matches the tile).
Similar restrictions apply to setting the property (and ``width`` &
``height``).
.. attribute:: size
Size of this object, as a (width, height) tuple, in units of map
tiles.
Shares setting restrictions with ``pixel_size``.
Note that the constructor will nly accept one of ``size`` or
``pixel_size``, not both at the same time.
Unpacked size attributes:
.. attribute:: width
.. attribute:: height
.. attribute:: pixel_width
.. attribute:: pixel_height
"""
objtype = 'ellipse'
@helpers.from_dict_method
[docs] def from_dict(cls, dct, layer):
assert dct.pop('ellipse')
size = dct.pop('width'), dct.pop('height')
dct['y'] = dct['y'] + size[1]
return super(EllipseObject, cls)._dict_helper(dct, layer, size)
[docs] def to_dict(self):
result = super(EllipseObject, self).to_dict()
result['ellipse'] = True
return result