177 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			177 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from typing import List, Tuple, Dict
 | |
| import copy
 | |
| import numpy
 | |
| from numpy import pi
 | |
| 
 | |
| from .error import PatternError, PatternLockedError
 | |
| from .utils import is_scalar, vector2, rotation_matrix_2d, layer_t
 | |
| 
 | |
| 
 | |
| class Label:
 | |
|     """
 | |
|     A text annotation with a position and layer (but no size; it is not drawn)
 | |
|     """
 | |
|     __slots__ = ('_offset', '_layer', '_string', 'identifier', 'locked')
 | |
| 
 | |
|     _offset: numpy.ndarray
 | |
|     """ [x_offset, y_offset] """
 | |
| 
 | |
|     _layer: layer_t
 | |
|     """ Layer (integer >= 0, or 2-Tuple of integers) """
 | |
| 
 | |
|     _string: str
 | |
|     """ Label string """
 | |
| 
 | |
|     identifier: Tuple
 | |
|     """ Arbitrary identifier tuple, useful for keeping track of history when flattening """
 | |
| 
 | |
|     locked: bool
 | |
|     """ If `True`, any changes to the label will raise a `PatternLockedError` """
 | |
| 
 | |
|     def __setattr__(self, name, value):
 | |
|         if self.locked and name != 'locked':
 | |
|             raise PatternLockedError()
 | |
|         object.__setattr__(self, name, value)
 | |
| 
 | |
|     # ---- Properties
 | |
|     # offset property
 | |
|     @property
 | |
|     def offset(self) -> numpy.ndarray:
 | |
|         """
 | |
|         [x, y] offset
 | |
|         """
 | |
|         return self._offset
 | |
| 
 | |
|     @offset.setter
 | |
|     def offset(self, val: vector2):
 | |
|         if not isinstance(val, numpy.ndarray):
 | |
|             val = numpy.array(val, dtype=float)
 | |
| 
 | |
|         if val.size != 2:
 | |
|             raise PatternError('Offset must be convertible to size-2 ndarray')
 | |
|         self._offset = val.flatten().astype(float)
 | |
| 
 | |
|     # layer property
 | |
|     @property
 | |
|     def layer(self) -> layer_t:
 | |
|         """
 | |
|         Layer number or name (int, tuple of ints, or string)
 | |
|         """
 | |
|         return self._layer
 | |
| 
 | |
|     @layer.setter
 | |
|     def layer(self, val: layer_t):
 | |
|         self._layer = val
 | |
| 
 | |
|     # string property
 | |
|     @property
 | |
|     def string(self) -> str:
 | |
|         """
 | |
|         Label string (str)
 | |
|         """
 | |
|         return self._string
 | |
| 
 | |
|     @string.setter
 | |
|     def string(self, val: str):
 | |
|         self._string = val
 | |
| 
 | |
|     def __init__(self,
 | |
|                  string: str,
 | |
|                  offset: vector2 = (0.0, 0.0),
 | |
|                  layer: layer_t = 0,
 | |
|                  locked: bool = False):
 | |
|         object.__setattr__(self, 'locked', False)
 | |
|         self.identifier = ()
 | |
|         self.string = string
 | |
|         self.offset = numpy.array(offset, dtype=float, copy=True)
 | |
|         self.layer = layer
 | |
|         self.locked = locked
 | |
| 
 | |
|     def __copy__(self) -> 'Label':
 | |
|         return Label(string=self.string,
 | |
|                      offset=self.offset.copy(),
 | |
|                      layer=self.layer,
 | |
|                      locked=self.locked)
 | |
| 
 | |
|     def  __deepcopy__(self, memo: Dict = None) -> 'Label':
 | |
|         memo = {} if memo is None else memo
 | |
|         new = copy.copy(self).unlock()
 | |
|         new._offset = self._offset.copy()
 | |
|         new.locked = self.locked
 | |
|         return new
 | |
| 
 | |
|     def copy(self) -> 'Label':
 | |
|         """
 | |
|         Returns a deep copy of the label.
 | |
|         """
 | |
|         return copy.deepcopy(self)
 | |
| 
 | |
|     def translate(self, offset: vector2) -> 'Label':
 | |
|         """
 | |
|         Translate the label by the given offset
 | |
| 
 | |
|         Args:
 | |
|             offset: [x_offset, y,offset]
 | |
| 
 | |
|         Returns:
 | |
|             self
 | |
|         """
 | |
|         self.offset += offset
 | |
|         return self
 | |
| 
 | |
|     def rotate_around(self, pivot: vector2, rotation: float) -> 'Label':
 | |
|         """
 | |
|         Rotate the label around a point.
 | |
| 
 | |
|         Args:
 | |
|             pivot: Point (x, y) to rotate around
 | |
|             rotation: Angle to rotate by (counterclockwise, radians)
 | |
| 
 | |
|         Returns:
 | |
|             self
 | |
|         """
 | |
|         pivot = numpy.array(pivot, dtype=float)
 | |
|         self.translate(-pivot)
 | |
|         self.offset = numpy.dot(rotation_matrix_2d(rotation), self.offset)
 | |
|         self.translate(+pivot)
 | |
|         return self
 | |
| 
 | |
|     def get_bounds(self) -> numpy.ndarray:
 | |
|         """
 | |
|         Return the bounds of the label.
 | |
| 
 | |
|         Labels are assumed to take up 0 area, i.e.
 | |
|         bounds = [self.offset,
 | |
|                   self.offset]
 | |
| 
 | |
|         Returns:
 | |
|             Bounds [[xmin, xmax], [ymin, ymax]]
 | |
|         """
 | |
|         return numpy.array([self.offset, self.offset])
 | |
| 
 | |
|     def lock(self) -> 'Label':
 | |
|         """
 | |
|         Lock the Label, causing any modifications to raise an exception.
 | |
| 
 | |
|         Return:
 | |
|             self
 | |
|         """
 | |
|         self.offset.flags.writeable = False
 | |
|         object.__setattr__(self, 'locked', True)
 | |
|         return self
 | |
| 
 | |
|     def unlock(self) -> 'Label':
 | |
|         """
 | |
|         Unlock the Label, re-allowing changes.
 | |
| 
 | |
|         Return:
 | |
|             self
 | |
|         """
 | |
|         object.__setattr__(self, 'locked', False)
 | |
|         self.offset.flags.writeable = True
 | |
|         return self
 | |
| 
 | |
|     def __repr__(self) -> str:
 | |
|         locked = ' L' if self.locked else ''
 | |
|         return f'<Label "{self.string}" l{self.layer} o{self.offset}{locked}>'
 |