Source code for CoolWorld.shapes

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""Shapes module.

Implements:

    class WorldShapeBase2D -- mixin base class for 2D shape management.

    class WorldShapeBase3D -- mixin base class for 3D shape management.

    class WorldShapeBox3D -- shape specific class for managing 3D box
    objects.

    class WorldShapeCircle2D -- shape specific class for managing 2D
    circle objects.

    class WorldShapeImage2D -- shape specific class for managing 2D
    image objects.

    class WorldShapePolygon2D -- shape specific class for managing 2D
    polygon objects.

    class WorldShapePolyhedron3D -- shape specific class for managing
    3D polyhedron objects.

    class WorldShapeRectangle2D -- shape specific class for managing 2D
    rectangle objects.

    class WorldShapeSphere3D -- shape specific class for managing 3D
    sphere objects.
"""

"""
    The MIT License (MIT)

    Copyright (c) 2015 Raphaël SEBAN

    Permission is hereby granted, free of charge, to any person
    obtaining a copy of this software and associated documentation
    files (the "Software"), to deal in the Software without
    restriction, including without limitation the rights to use, copy,
    modify, merge, publish, distribute, sublicense, and/or sell copies
    of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be
    included in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
    BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
    ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
    CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.
"""

try:
    from . import geometry
except:
    import geometry
# end try


[docs]class WorldShapeBase2D (geometry.WorldAnchorage2D): """Mixin base class for 2D shape management.""" _OFFSETS = ( (0, 0), (-1, 0), (0, -1), (-1, -1), (1, -1), (0, 1), (-1, 1), (1, 1), (1, 0) ) def __getitem__ (self, key): """Make object iterable.""" return self.to_tuple()[key] # end def def __init__ (self, **options): """Class constructor. Parameter: options -- keyword arguments. Supported keyword arguments: anchor -- anchorage query (default: CENTER). origin -- anchorage point of origin (default: None). unit_vector -- local unit vector (default: (1, 1)). Notice: options are saved into self.options member attribute. """ super().__init__(**options) self.ANCHOR_OFFSETS = dict( zip(self.LEGAL_ANCHORS, self._OFFSETS) ) # end def def __repr__ (self): """Object string representation.""" return str(self.to_tuple()) # end def @property
[docs] def bbox (self): """Bounding box - read-only attribute. This bounding box is relative to self.origin anchorage point and to self.anchor anchorage query. """ _center = self.center_point _hs = self.half_size return self.bbox_type(_center - _hs, _center + _hs) # end def
@property
[docs] def bbox_type (self): """Preferred bounding box type - read-only attribute. Current implementation returns geometry.WorldBoundingBox2D class type. This can be redefined in subclasses to fit more specific needs. """ return geometry.WorldBoundingBox2D # end def
@property
[docs] def center_point (self): """Shape's center point - read-only attribute. This point is relative to self.origin anchorage point and to self.anchor anchorage query. Uses self.unit_vector for local calculations. """ _offset = self.unit_vector.cdot( self.ANCHOR_OFFSETS[self.anchor] ) return (self.origin + _offset.cdot(self.half_size)) # end def
@property
[docs] def half_size (self): """Half dims of size - read-only attribute. Returns half dims of size i.e. (width//2, height//2). """ _w, _h = self.size return (_w//2, _h//2) # end def
[docs] def points_to_tuple (self, *points): """Convert a list of given points to a tuple list of tuple coordinates. Example: [Point(1, 2), Point(3, 4), (5, 6), [7, 8]] will be converted to ((1, 2), (3, 4), (5, 6), (7, 8)). """ return tuple(map(tuple, points)) # end def
@property
[docs] def size (self): """Shape's size - read-only attribute. This attribute MUST be overridden in subclasses. Will raise a NotImplementedError otherwise. """ # returns size of shape object raise NotImplementedError # end def
[docs] def to_tuple (self): """Tuple representation of shape object attributes. This attribute MUST be overridden in subclasses. Will raise a NotImplementedError otherwise. """ # tuple representation of shape object attributes raise NotImplementedError # end def # end class
[docs]class WorldShapeBase3D (geometry.WorldAnchorage3D, WorldShapeBase2D): """Mixin base class for 3D shape management.""" _OFFSETS = tuple( a + (b,) for b in (-1, 0, 1) for a in WorldShapeBase2D._OFFSETS ) def __init__ (self, **options): """Class constructor. Parameter: options -- keyword arguments. Supported keyword arguments: anchor -- anchorage query (default: MIDDLE + CENTER). origin -- anchorage point of origin (default: None). unit_vector -- local unit vector (default: (1, 1, 1)). Notice: options are saved into self.options member attribute. """ geometry.WorldAnchorage3D.__init__(self, **options) WorldShapeBase2D.__init__(self, **options) # end def @property
[docs] def bbox_type (self): """Preferred bounding box type - read-only attribute. Current implementation returns geometry.WorldBoundingBox3D class type. This can be redefined in subclasses to fit more specific needs. """ return geometry.WorldBoundingBox3D # end def
@property
[docs] def half_size (self): """Half dims of size - read-only attribute. Returns half dims of size i.e. (width//2, height//2, depth//2). """ _w, _h, _d = self.size return (_w//2, _h//2, _d//2) # end def # end class
[docs]class WorldShapeImage2D (WorldShapeBase2D): """Shape specific class for managing 2D image objects.""" def __init__ (self, origin, image, **options): """Class constructor. Parameter: origin -- tuple coordinates (x, y) or point object for object's anchorage point of origin. image -- image object. options -- keyword arguments. Supported keyword arguments: anchor -- anchorage query (default: CENTER). unit_vector -- local unit vector (default: (1, 1)). Notice: options are saved into self.options member attribute. """ super().__init__(origin=origin, **options) self.image = image # end def @property
[docs] def height (self): """Image height - read-only attribute.""" return self.image.height() # end def
@property
[docs] def size (self): """Image size - read-only attribute. Returns a tuple (width, height). """ return (self.width, self.height) # end def
[docs] def to_tuple (self): """Tuple representation of shape object attributes.""" return self.bbox.to_tuple() # end def
@property
[docs] def width (self): """Image width - read-only attribute.""" return self.image.width() # end def # end class
[docs]class WorldShapeCircle2D (WorldShapeBase2D): """Shape specific class for managing 2D circle objects.""" def __init__ (self, origin, radius, **options): """Class constructor. Parameter: origin -- tuple coordinates (x, y) or point object for object's anchorage point of origin. radius -- numeric value for circle's radius. options -- keyword arguments. Supported keyword arguments: anchor -- anchorage query (default: CENTER). unit_vector -- local unit vector (default: (1, 1)). Notice: options are saved into self.options member attribute. """ super().__init__(origin=origin, **options) self.radius = radius # end def @property
[docs] def size (self): """Circle size - read-only attribute. Returns a tuple (width, height). """ _diameter = 2 * self.radius # returns (width, height) return (_diameter, _diameter) # end def
[docs] def to_tuple (self): """Tuple representation of shape object attributes.""" return (self.center_point, self.radius) # end def # end class
[docs]class WorldShapeSphere3D (WorldShapeCircle2D, WorldShapeBase3D): """Shape specific class for managing 3D sphere objects.""" def __init__ (self, origin, radius, **options): """Class constructor. Parameter: origin -- tuple coordinates (x, y, z) or point object for object's anchorage point of origin. radius -- numeric value for sphere's radius. options -- keyword arguments. Supported keyword arguments: anchor -- anchorage query (default: MIDDLE + CENTER). unit_vector -- local unit vector (default: (1, 1, 1)). Notice: options are saved into self.options member attribute. """ WorldShapeBase3D.__init__(self, **options) WorldShapeCircle2D.__init__(self, origin, radius, **options) # end def @property
[docs] def size (self): """Sphere size - read-only attribute. Returns a tuple (width, height, depth). """ _diameter = 2 * self.radius # returns (width, height, depth) return (_diameter, _diameter, _diameter) # end def # end class
[docs]class WorldShapePolygon2D (WorldShapeBase2D): """Shape specific class for managing 2D polygon objects.""" def __init__ (self, origin, *coords, **options): """Class constructor. Parameters: origin -- tuple coordinates (x, y) or point object for object's anchorage point of origin. coords -- variable list of tuple coordinates (x, y) or point objects representing polygon's vertices. options -- keyword arguments. Supported keyword arguments: anchor -- anchorage query (default: CENTER). unit_vector -- local unit vector (default: (1, 1)). Notice: options are saved into self.options member attribute. """ super().__init__(origin=origin, **options) try: self.coords = coords or options.get("coords") except: raise ValueError( "Missing list of coordinates to build " "a consistent polygon." ) from None # end try # end def @property def coords (self): """Polygon coordinates attribute (read-write). Stores any series of point coordinates as a tuple list of tuple coordinates (simplest form). """ return self.__coords # end def @coords.setter
[docs] def coords (self, values): self.__coords = self.points_to_tuple(*values) # end def
@property
[docs] def rel_coords (self): """Relative coordinates - read-only attribute. These coordinates are relative to 'center_point' attribute and to 'unit_vector' attribute. """ _u = self.unit_vector _center = self.center_point return self.points_to_tuple( *(_center + _u.cdot(coords) for coords in self.coords) ) # end def
@property
[docs] def rel_coords_flat (self): """Flat relative coordinates - read-only attribute. All relative coordinates are linearized into a single tuple. This converts a ((x1, y1), ..., (xn, yn)) tuple list of tuple coordinates to a single (x1, y1, ..., xn, yn) tuple of flat coordinates. """ return tuple( dim for coords in self.rel_coords for dim in coords ) # end def
@property
[docs] def size (self): """Polygon size - read-only attribute. Returns a tuple (width, height) of polygon's bounding box. """ return tuple( max(dims) - min(dims) for dims in zip(*self.coords) ) # end def
[docs] def to_tuple (self): """Tuple representation of shape object attributes.""" return self.rel_coords # end def # end class
[docs]class WorldShapePolyhedron3D (WorldShapePolygon2D, WorldShapeBase3D): """Shape specific class for managing 3D polyhedron objects.""" def __init__ (self, origin, *coords, **options): """Class constructor. Parameters: origin -- tuple coordinates (x, y, z) or point object for object's anchorage point of origin. coords -- variable list of tuple coordinates (x, y, z) or point objects representing polyhedron's vertices. options -- keyword arguments. Supported keyword arguments: anchor -- anchorage query (default: MIDDLE + CENTER). unit_vector -- local unit vector (default: (1, 1, 1)). Notice: options are saved into self.options member attribute. """ WorldShapeBase3D.__init__(self, **options) WorldShapePolygon2D.__init__(self, origin, *coords, **options) # end def # end class
[docs]class WorldShapeRectangle2D (geometry.WorldBoxBase2D, WorldShapeBase2D): """Shape specific class for managing 2D rectangle objects.""" def __init__ (self, origin, width, height, **options): """Class constructor. Parameters: origin -- tuple coordinates (x, y) or point object for object's anchorage point of origin. width -- rectangle's width. height -- rectangle's height. options -- keyword arguments. Supported keyword arguments: anchor -- anchorage query (default: CENTER). unit_vector -- local unit vector (default: (1, 1)). Notice: options are saved into self.options member attribute. """ geometry.WorldBoxBase2D.__init__( self, width, height, **options ) WorldShapeBase2D.__init__(self, origin=origin, **options) # end def
[docs] def to_tuple (self): """Tuple representation of shape object attributes.""" return self.bbox.to_tuple() # end def # end class
[docs]class WorldShapeBox3D (geometry.WorldBoxBase3D, WorldShapeBase3D): """Shape specific class for managing 3D box objects.""" def __init__ (self, origin, width, height, depth, **options): """Class constructor. Parameters: origin -- tuple coordinates (x, y, z) or point object for object's anchorage point of origin. width -- box' width. height -- box' height. depth -- box' depth. options -- keyword arguments. Supported keyword arguments: anchor -- anchorage query (default: MIDDLE + CENTER). unit_vector -- local unit vector (default: (1, 1, 1)). Notice: options are saved into self.options member attribute. """ geometry.WorldBoxBase3D.__init__( self, width, height, depth, **options ) WorldShapeBase3D.__init__(self, origin=origin, **options) # end def
[docs] def to_tuple (self): """Tuple representation of shape object attributes.""" return self.bbox.to_tuple() # end def # end class
[docs]def run_demo (): import tkinter as tk _root = tk.Tk() _root.title("CoolWorld.shapes tkinter demo") _root.resizable(0, 0) _canvas = tk.Canvas(_root, width=400, height=250, bg="black") _canvas.unit_vector = (1, -1) def show_shape (shape, shape_factory): _cr = (2, 2) for _a in shape.LEGAL_ANCHORS: _canvas.create_text( tuple(shape.origin + (0, -30)), text=_a.upper(), fill="white" ) shape.anchor = _a shape_factory(shape) _origin = shape.origin _canvas.create_oval( shape.points_to_tuple(_origin - _cr, _origin + _cr), fill="cyan" ) shape.origin += (40, 0) # end for # end def _shape = WorldShapeCircle2D( origin=(35, 50), radius=10, unit_vector=_canvas.unit_vector, ) _factory = ( lambda shape: _canvas.create_oval( shape.bbox.to_flat_tuple(), fill="yellow" ) ) show_shape(_shape, _factory) _shape = WorldShapeRectangle2D( origin=(35, 130), width=20, height=10, unit_vector=_canvas.unit_vector, ) _factory = ( lambda shape: _canvas.create_rectangle( shape.bbox.to_flat_tuple(), fill="orange" ) ) show_shape(_shape, _factory) _shape = WorldShapePolygon2D( origin=(35, 210), coords=( (9.51, 3.09), (-9.51, 3.09), (5.88, -8.09), (0.0, 10.0), (-5.88, -8.09) ), unit_vector=_canvas.unit_vector, ) _factory = ( lambda shape: _canvas.create_polygon(shape.rel_coords, fill="magenta") ) show_shape(_shape, _factory) _canvas.pack() _root.mainloop() # end def
if __name__ == "__main__": run_demo() # end if