瀏覽代碼

update geometry and pencil documentation, add PaintPotPencil class to pencil.py

olinox 9 年之前
父節點
當前提交
51fb9e1729
共有 4 個文件被更改,包括 183 次插入55 次删除
  1. 9 1
      pypog/Grid.py
  2. 7 4
      pypog/Piece.py
  3. 16 16
      pypog/geometry.py
  4. 151 34
      pypog/pencil.py

+ 9 - 1
pypog/Grid.py

@@ -7,6 +7,14 @@ from pypog import geometry
 from pypog import pathfinder
 from pypog import pathfinder
 
 
 
 
+ORIGIN_HPOSITION_LEFT = 0
+ORIGIN_HPOSITION_MIDDLE = 1
+ORIGIN_HPOSITION_RIGHT = 2
+ORIGIN_VPOSITION_TOP = 10
+ORIGIN_VPOSITION_MIDDLE = 11
+ORIGIN_VPOSITION_BOTTOM = 12
+
+
 class Grid(object):
 class Grid(object):
     def __init__(self, cell_shape, width, height, roof = None):
     def __init__(self, cell_shape, width, height, roof = None):
         self._cell_shape = None
         self._cell_shape = None
@@ -108,7 +116,7 @@ class Grid(object):
     def path(self, x1, y1, x2, y2):
     def path(self, x1, y1, x2, y2):
         return pathfinder.path( self, (x1, y1), (x2,y2), self.moving_cost_function )
         return pathfinder.path( self, (x1, y1), (x2,y2), self.moving_cost_function )
 
 
-    
+
 class HexGrid(Grid):
 class HexGrid(Grid):
     def __init__(self, width, height):
     def __init__(self, width, height):
         Grid.__init__(self, geometry.HEX, width, height)
         Grid.__init__(self, geometry.HEX, width, height)

+ 7 - 4
pypog/Piece.py

@@ -134,13 +134,16 @@ class Piece(object):
         occupation = self.occupation()
         occupation = self.occupation()
         return [(x, y, z) for x, y in occupation for z in range(dz + self.zR, dz + self.zR + self.height)]
         return [(x, y, z) for x, y in occupation for z in range(dz + self.zR, dz + self.zR + self.height)]
 
 
-    def move(self, x, y, zR = None):
-        """move the piece to (x, y) position and zR relative altitude"""
+    def move_to(self, x, y, zR = None):
+        """move the piece to (x, y) position and is requested to zR relative altitude"""
         self.position = x, y
         self.position = x, y
         if zR != None:
         if zR != None:
             self.zR = zR
             self.zR = zR
     
     
     def rotate(self, i):
     def rotate(self, i):
-        """pivot the Piece i times (counterclockwise rotation)"""
-        self.rotation += i
+        """pivot the Piece i times (counterclockwise rotation, i can be negative)"""
+        new_rotation = self.rotation + i
+        self.rotation = new_rotation % self.cell_shape
+        
+        
         
         

+ 16 - 16
pypog/geometry.py

@@ -1,9 +1,6 @@
 '''
 '''
-Created on 5 dec. 2016
     Geometric functions on hexagonal or square grids
     Geometric functions on hexagonal or square grids
-    
-    > Line algorithm is an implementation of bresenham algorithm
-    
+
     2D functions return lists of (x, y) coordinates
     2D functions return lists of (x, y) coordinates
     3D functions return lists of (x, y, z) coordinates
     3D functions return lists of (x, y, z) coordinates
     
     
@@ -15,8 +12,9 @@ Created on 5 dec. 2016
     * hollow_rect function return the list of the cells on the borders of the rectangle between the cells (x1, y1), (x2, y1), , (x2, y2) and , (x1, y2)
     * hollow_rect function return the list of the cells on the borders of the rectangle between the cells (x1, y1), (x2, y1), , (x2, y2) and , (x1, y2)
     * triangle function return the list of the cells in a triangle from its apex (xa, ya) to its base (xh, yh)
     * triangle function return the list of the cells in a triangle from its apex (xa, ya) to its base (xh, yh)
     * triangle3d function return the list of the cells in a cone from its apex (xa, ya, za) to its base (xh, yh, zh)
     * triangle3d function return the list of the cells in a cone from its apex (xa, ya, za) to its base (xh, yh, zh)
+    * pivot function return a list of coordinates after a counterclockwise rotation of a given list of coordinates, around a given center
     
     
-@author: olinox
+@author: olinox14
 '''
 '''
 from math import sqrt
 from math import sqrt
 
 
@@ -26,9 +24,10 @@ SQUARE = 4
 HEX = 6
 HEX = 6
 ANGLES = (1, 2, 3)
 ANGLES = (1, 2, 3)
 
 
-## neigbours
+## neighbours
 
 
 def neighbours_of(cell_shape, x, y):
 def neighbours_of(cell_shape, x, y):
+    """ returns the list of coords of the neighbours of a cell"""
     if cell_shape == SQUARE:
     if cell_shape == SQUARE:
         return squ_neighbours_of(x, y)
         return squ_neighbours_of(x, y)
     elif cell_shape == HEX: 
     elif cell_shape == HEX: 
@@ -291,6 +290,9 @@ def hollow_rect(x1, y1, x2, y2):
 
 
 
 
 def triangle(cell_shape, xa, ya, xh, yh, iAngle):
 def triangle(cell_shape, xa, ya, xh, yh, iAngle):
+    """Returns a list of (x, y) coordinates in a triangle
+    A is the top of the triangle, H if the middle of the base
+    """
     if cell_shape == SQUARE:
     if cell_shape == SQUARE:
         return triangle_sq(xa, ya, xh, yh, iAngle)
         return triangle_sq(xa, ya, xh, yh, iAngle)
     elif cell_shape == HEX: 
     elif cell_shape == HEX: 
@@ -299,6 +301,9 @@ def triangle(cell_shape, xa, ya, xh, yh, iAngle):
         raise ValueError("'geometry' has to be a value from GRID_GEOMETRIES")
         raise ValueError("'geometry' has to be a value from GRID_GEOMETRIES")
 
 
 def triangle3d(cell_shape, xa, ya, za, xh, yh, zh, iAngle):
 def triangle3d(cell_shape, xa, ya, za, xh, yh, zh, iAngle):
+    """Returns a list of (x, y, z) coordinates in a 3d-cone
+    A is the top of the cone, H if the center of the base
+    """
     if cell_shape == SQUARE:
     if cell_shape == SQUARE:
         return triangle_sq_3d(xa, ya, za, xh, yh, zh, iAngle)
         return triangle_sq_3d(xa, ya, za, xh, yh, zh, iAngle)
     elif cell_shape == HEX: 
     elif cell_shape == HEX: 
@@ -600,14 +605,9 @@ def hex_distance_cube(xa, ya, za, xb, yb, zb):
     return max(abs(xa - xb), abs(ya - yb), abs(za - zb))
     return max(abs(xa - xb), abs(ya - yb), abs(za - zb))
 
 
 def distance_off(xa, ya, xb, yb):
 def distance_off(xa, ya, xb, yb):
-        """ distance between A and B (offset coordinates)"""
-        # 10 times quicker if no conversion...
-        xua, yua, zua = cv_off_cube(xa, ya)
-        xub, yub, zub = cv_off_cube(xb, yb)
-        return max(abs(xua - xub), abs(yua - yub), abs(zua - zub))
-    
-
-
-    
-    
+    """ distance between A and B (offset coordinates)"""
+    # 10 times quicker if no conversion...
+    xua, yua, zua = cv_off_cube(xa, ya)
+    xub, yub, zub = cv_off_cube(xb, yb)
+    return max(abs(xua - xub), abs(yua - yub), abs(zua - zub))
     
     

+ 151 - 34
pypog/pencil.py

@@ -1,100 +1,217 @@
 '''
 '''
-Created on 5 déc. 2016
-
+Created on 5 dec. 2016
+    Pencil classes allow you to get an evolving selection of coordinates, according to the type of pencil you use.
+    
+    Start it, set size (default: 1), update it as many times you want with new positions on the grid, then
+    get the list of selected coordinates, or the list of added / removed coorinates by the last update.
+    
+    Connect those functions to your graphical grid, and let it do the job.
+    
+    Example of use:
+        On mouse left button down on a (x, y) cell: pencil.start(x, y)
+        On hover event on cells: pencil.update(), then display the result on your grid
+        On mouse left button up: Apply the modification on your grid, then delete your pencil object
+        
 @author: olinox
 @author: olinox
 '''
 '''
 from abc import ABCMeta
 from abc import ABCMeta
 
 
-from pypog.geometry import line2d, zone
+from prompt_toolkit import selection
+
+from pypog import geometry
+from pypog.Grid import HexGrid
 
 
 
 
 class BasePencil(object):
 class BasePencil(object):
+    """Base class of all pencils
+    This is an abstract class: override it!"""
     __metaclass__ = ABCMeta
     __metaclass__ = ABCMeta
     
     
     def __init__(self, grid):
     def __init__(self, grid):
+        
+        # do we really need the grid ref? cell_shape could be enough?
         self._grid = grid
         self._grid = grid
         
         
         self._origin = None
         self._origin = None
         self._position = None
         self._position = None
+        self._positions_history = []
         
         
         self._size = 1
         self._size = 1
-        self._selection = []
+        self._selection = set()
 
 
-        self._added = []
-        self._removed = []
+        self._added = set()
+        self._removed = set()
 
 
     @property
     @property
     def origin(self):
     def origin(self):
-        return self._coord0
-
-    @origin.setter
-    def origin(self, x, y):
-        self._origin = (x, y)
+        """returns the coordinates of the cell where the painting began
+        This property is read-only: use the 'start' method to set it"""
+        return self._origin
 
 
     @property
     @property
     def size(self):
     def size(self):
+        """returns the current size of the pencil"""
         return self._size
         return self._size
 
 
     @size.setter
     @size.setter
     def size(self, size):
     def size(self, size):
+        """sets the current size of the pencil
+        size has to be a strictly positive integer"""
+        if not isinstance(size, int):
+            raise TypeError("size has to be an integer (given: {})".format(size))
         if not size > 0:
         if not size > 0:
-            raise ValueError("size has to be strictly positive")
+            raise ValueError("size has to be strictly positive (given: {})".format(size))
         self._size = size
         self._size = size
 
 
     @property
     @property
     def position(self):
     def position(self):
+        """returns the current (x, y) position of the pencil
+        This property is read-only: use the 'update' method to give it a new value"""
         return self._position
         return self._position
 
 
-    @position.setter
-    def position(self, x, y):
-        self._position = (x, y)
-        self._update()
-
     @property
     @property
     def selection(self):
     def selection(self):
-        return self._selection
+        """return the current list of coordinates selected by the pencil (read-only)"""
+        return list( self._selection )
         
         
     @property
     @property
     def added(self):
     def added(self):
-        return self._added
+        """return the list of coordinates added to the last selection by the last update (read-only)
+        
+        This property exists for performances reasons: you probably don't want to update your whole graphic scene if 
+        just one or two cells where added or removed from the selection!
+        """
+        return list( self._added )
 
 
     @property
     @property
     def removed(self):
     def removed(self):
-        return self._removed
+        """return the list of coordinates removed from the last selection by the last update (read-only)
+                
+        This property exists for performances reasons: you probably don't want to update your whole graphic scene if 
+        just one or two cells where added or removed from the selection!
+        """
+        return list( self._removed )
 
 
     def _update(self):
     def _update(self):
-        pass
-        
+        """this method update the selection, added and removed lists, according to the current size, position, origin
+        Override it to create your own pencil"""
+        # this method should be overrided to update : self._selection, self._added, self._removed
+        raise NotImplementedError("_update method has to be overrided")
+
+    def start(self, x0, y0):
+        """start a new painting
+        (x0, y0) is the origin of the painting, and will not be modified."""
+        if len(self._selection) > 0:
+            raise Exception("the pencil has already been started")
+        self._origin = (x0, y0)
+        self.update(x0, y0)
+
+    def update(self, x, y):
+        """Updates the current position of the pencil
+        If (x, y) is not in the position's history, it updates the current selection, and the added / removed lists."""
+        if not all(isinstance(var, int) for var in (x, y)):
+            raise TypeError("x, y has to be an integers (given: {})".format((x, y)))
+        if self._origin == None:
+            raise Exception("Pencil has to be started before any update: use 'start' method")
+        self._position = (x, y)
+        if not (x, y) in  self._positions_history:
+            self._positions_history.append( (x, y) )
+            self._update()
         
         
 class LinePencil(BasePencil):
 class LinePencil(BasePencil):
+    """Paint a 2d line between origin and position"""
     def __init__(self, *args):
     def __init__(self, *args):
-        BasePencil.__init__(*args)
+        BasePencil.__init__(self, *args)
         
         
     def _update(self):
     def _update(self):
-        x0, y0 = self.origin
-        x, y = self.position
+        x0, y0 = self._origin
+        x, y = self._position
         
         
+        # use a set because of performance (should we generalize the use of sets for coordinates lists?)
         result = set([])
         result = set([])
-        line = line2d(self._grid.cell_shape, x0, y0, x, y)
+        
+        line = set( geometry.line2d(self._grid.cell_shape, x0, y0, x, y) )
+        
+        # apply size with geometry.zone
         for x, y in line:
         for x, y in line:
-            result |= set( zone(self._grid.cell_shape, x, y, self.size) )
+            result |= set( geometry.zone(self._grid.cell_shape, x, y, self.size) )
         
         
-        self._added = list( result - self._selection )
-        self._removed = list( self._selection - result ) 
-        self._selection = list( result )
+        self._added = result - self._selection
+        self._removed = self._selection - result
+        self._selection = result
 
 
 
 
 class SimplePencil(BasePencil):
 class SimplePencil(BasePencil):
+    """Free handed pencil"""
     def __init__(self, *args):
     def __init__(self, *args):
-        BasePencil.__init__(*args)
+        BasePencil.__init__(self, *args)
         
         
     def _update(self):
     def _update(self):
         x, y = self.position
         x, y = self.position
-        zone = zone(self._grid.cell_shape, x, y, self.size)
+        zone_set = set( geometry.zone(self._grid.cell_shape, x, y, self.size) )
 
 
-        self._added = list( set(zone) - set(self._selection) )
-        self._selection = list( set(self._selection) + set(zone))
+        self._added = zone_set - self._selection
+        # there can't be any removed coordinates with this pencil
+        self._selection = self._selection + zone_set
         
         
         
         
+class PaintPotPencil(BasePencil):
+    """This particular pencil selects all cells of same nature from nearest to nearest
+    
+    The 'same nature' is confirmed by passing a comparaison method to the pencil
+    This method should take x1, y1, x2, y2 as args, and return a boolean: True for similar cells, False in other case
+    
+    WARNING: this pencil result is not modified by the update method, because the result is already defined when it starts
+    
+    WARNING 2: take care of what you put in your comparing method, it could end with a infinite loop if you are not working on a finite grid!
+    
+    example of use:
+        
+        # your comparison function
+        my_grid = HexGrid(30,30)
+        
+        def identical_cells_function(x1, y1, x2, y2):
+            if my_grid.cell(x1, y1).color == my_grid.cell(x2, y2).color:
+                return True
+            return False
+            
+        my_pencil = PaintPotPencil(my_grid)
+        my_pencil.start(3, 3, identical_cells_function)
+        
+        print(my_pencil.selection)
+    
+    """
+    
+    def __init__(self, *args):
+        BasePencil.__init__(self, *args)
+        self._comparing_method = (lambda x: False)
+
+    def start(self, x0, y0, comparing_method_pointer):
+        self._comparing_method_pointer = comparing_method_pointer
+        BasePencil.start(self, x0, y0)
+
+    def update(self, *args):
+        if not len(self._selection) > 0:
+            BasePencil.update(self, *args)
+
+    @property
+    def added(self):
+        return self._selection
+
+    def _update(self):
+        x0, y0 = self._origin 
+        current_selection = { (x0, y0) }
+        buffer = set( geometry.neighbours_of(self._grid._cell_shape, x0, y0) )
+        
+        while len(buffer) > 0:
+            x, y = buffer.pop()
+            if self._comparing_method_pointer(x0, y0, x, y):
+                current_selection.add( (x, y) )
+                buffer |= ( set( geometry.neighbours_of(self._grid._cell_shape, x, y) ) - current_selection)
+                
+        self._selection = current_selection
+        
+        
+