allow locking of all objects
This commit is contained in:
		
							parent
							
								
									09711116a7
								
							
						
					
					
						commit
						e0db621595
					
				@ -25,7 +25,7 @@
 | 
			
		||||
 | 
			
		||||
import pathlib
 | 
			
		||||
 | 
			
		||||
from .error import PatternError
 | 
			
		||||
from .error import PatternError, PatternLockedError
 | 
			
		||||
from .shapes import Shape
 | 
			
		||||
from .label import Label
 | 
			
		||||
from .subpattern import SubPattern
 | 
			
		||||
 | 
			
		||||
@ -7,3 +7,11 @@ class PatternError(Exception):
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return repr(self.value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PatternLockedError(PatternError):
 | 
			
		||||
    """
 | 
			
		||||
    Exception raised when trying to modify a locked pattern
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        PatternError.__init__(self, 'Tried to modify a locked Pattern, subpattern, or shape')
 | 
			
		||||
 | 
			
		||||
@ -76,7 +76,7 @@ def write(patterns: Pattern or List[Pattern],
 | 
			
		||||
    :param library_name: Library name written into the GDSII file.
 | 
			
		||||
        Default 'masque-gdsii-write'.
 | 
			
		||||
    :param modify_originals: If True, the original pattern is modified as part of the writing
 | 
			
		||||
        process. Otherwise, a copy is made.
 | 
			
		||||
        process. Otherwise, a copy is made and deepunlock()-ed.
 | 
			
		||||
        Default False.
 | 
			
		||||
    :param disambiguate_func: Function which takes a list of patterns and alters them
 | 
			
		||||
        to make their names valid and unique. Default is `disambiguate_pattern_names`, which
 | 
			
		||||
@ -90,7 +90,7 @@ def write(patterns: Pattern or List[Pattern],
 | 
			
		||||
        disambiguate_func = disambiguate_pattern_names
 | 
			
		||||
 | 
			
		||||
    if not modify_originals:
 | 
			
		||||
        patterns = copy.deepcopy(patterns)
 | 
			
		||||
        patterns = [p.deepcopy().deepunlock() for p in patterns]
 | 
			
		||||
 | 
			
		||||
    # Create library
 | 
			
		||||
    lib = gdsii.library.Library(version=600,
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@ import copy
 | 
			
		||||
import numpy
 | 
			
		||||
from numpy import pi
 | 
			
		||||
 | 
			
		||||
from . import PatternError
 | 
			
		||||
from .error import PatternError, PatternLockedError
 | 
			
		||||
from .utils import is_scalar, vector2, rotation_matrix_2d
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -12,9 +12,9 @@ __author__ = 'Jan Petykiewicz'
 | 
			
		||||
 | 
			
		||||
class Label:
 | 
			
		||||
    """
 | 
			
		||||
    A circle, which has a position and radius.
 | 
			
		||||
    A text annotation with a position and layer (but no size; it is not drawn)
 | 
			
		||||
    """
 | 
			
		||||
    __slots__ = ('_offset', '_layer', '_string', 'identifier')
 | 
			
		||||
    __slots__ = ('_offset', '_layer', '_string', 'identifier', 'locked')
 | 
			
		||||
    # [x_offset, y_offset]
 | 
			
		||||
    _offset: numpy.ndarray
 | 
			
		||||
 | 
			
		||||
@ -27,6 +27,13 @@ class Label:
 | 
			
		||||
    # Arbitrary identifier tuple
 | 
			
		||||
    identifier: Tuple
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
@ -78,11 +85,20 @@ class Label:
 | 
			
		||||
    def __init__(self,
 | 
			
		||||
                 string: str,
 | 
			
		||||
                 offset: vector2=(0.0, 0.0),
 | 
			
		||||
                 layer: int=0):
 | 
			
		||||
                 layer: int=0,
 | 
			
		||||
                 locked: bool = False):
 | 
			
		||||
        self.unlock()
 | 
			
		||||
        self.identifier = ()
 | 
			
		||||
        self.string = string
 | 
			
		||||
        self.offset = numpy.array(offset, dtype=float)
 | 
			
		||||
        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
 | 
			
		||||
@ -134,4 +150,20 @@ class Label:
 | 
			
		||||
        """
 | 
			
		||||
        return numpy.array([self.offset, self.offset])
 | 
			
		||||
 | 
			
		||||
    def lock(self) -> 'Label':
 | 
			
		||||
        """
 | 
			
		||||
        Lock the Label
 | 
			
		||||
 | 
			
		||||
        :return: self
 | 
			
		||||
        """
 | 
			
		||||
        object.__setattr__(self, 'locked', True)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def unlock(self) -> 'Label':
 | 
			
		||||
        """
 | 
			
		||||
        Unlock the Label
 | 
			
		||||
 | 
			
		||||
        :return: self
 | 
			
		||||
        """
 | 
			
		||||
        object.__setattr__(self, 'locked', False)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@ from .repetition import GridRepetition
 | 
			
		||||
from .shapes import Shape, Polygon
 | 
			
		||||
from .label import Label
 | 
			
		||||
from .utils import rotation_matrix_2d, vector2, normalize_mirror
 | 
			
		||||
from .error import PatternError
 | 
			
		||||
from .error import PatternError, PatternLockedError
 | 
			
		||||
 | 
			
		||||
__author__ = 'Jan Petykiewicz'
 | 
			
		||||
 | 
			
		||||
@ -37,17 +37,19 @@ class Pattern:
 | 
			
		||||
            may reference the same Pattern object.
 | 
			
		||||
    :var name: An identifier for this object. Not necessarily unique.
 | 
			
		||||
    """
 | 
			
		||||
    __slots__ = ('shapes', 'labels', 'subpatterns', 'name')
 | 
			
		||||
    __slots__ = ('shapes', 'labels', 'subpatterns', 'name', 'locked')
 | 
			
		||||
    shapes: List[Shape]
 | 
			
		||||
    labels: List[Label]
 | 
			
		||||
    subpatterns: List[SubPattern or GridRepetition]
 | 
			
		||||
    name: str
 | 
			
		||||
    locked: bool
 | 
			
		||||
 | 
			
		||||
    def __init__(self,
 | 
			
		||||
                 name: str = '',
 | 
			
		||||
                 shapes: List[Shape] = (),
 | 
			
		||||
                 labels: List[Label] = (),
 | 
			
		||||
                 subpatterns: List[SubPattern] = (),
 | 
			
		||||
                 locked: bool = False,
 | 
			
		||||
                 ):
 | 
			
		||||
        """
 | 
			
		||||
        Basic init; arguments get assigned to member variables.
 | 
			
		||||
@ -57,7 +59,9 @@ class Pattern:
 | 
			
		||||
        :param labels: Initial labels in the Pattern
 | 
			
		||||
        :param subpatterns: Initial subpatterns in the Pattern
 | 
			
		||||
        :param name: An identifier for the Pattern
 | 
			
		||||
        :param locked: Whether to lock the pattern after construction
 | 
			
		||||
        """
 | 
			
		||||
        self.unlock()
 | 
			
		||||
        if isinstance(shapes, list):
 | 
			
		||||
            self.shapes = shapes
 | 
			
		||||
        else:
 | 
			
		||||
@ -74,14 +78,27 @@ class Pattern:
 | 
			
		||||
            self.subpatterns = list(subpatterns)
 | 
			
		||||
 | 
			
		||||
        self.name = name
 | 
			
		||||
        self.locked = locked
 | 
			
		||||
 | 
			
		||||
    def __setattr__(self, name, value):
 | 
			
		||||
        if self.locked and name != 'locked':
 | 
			
		||||
            raise PatternLockedError()
 | 
			
		||||
        object.__setattr__(self, name, value)
 | 
			
		||||
 | 
			
		||||
    def  __copy__(self, memo: Dict = None) -> 'Pattern':
 | 
			
		||||
        return Pattern(name=self.name,
 | 
			
		||||
                       shapes=copy.deepcopy(self.shapes),
 | 
			
		||||
                       labels=copy.deepcopy(self.labels),
 | 
			
		||||
                       subpatterns=[copy.copy(sp) for sp in self.subpatterns],
 | 
			
		||||
                       locked=self.locked)
 | 
			
		||||
 | 
			
		||||
    def  __deepcopy__(self, memo: Dict = None) -> 'Pattern':
 | 
			
		||||
        memo = {} if memo is None else memo
 | 
			
		||||
        new = copy.copy(self)
 | 
			
		||||
        new.name = self.name
 | 
			
		||||
        new.shapes = copy.deepcopy(self.shapes, memo)
 | 
			
		||||
        new.labels = copy.deepcopy(self.labels, memo)
 | 
			
		||||
        new.subpatterns = copy.deepcopy(self.subpatterns, memo)
 | 
			
		||||
        new = Pattern(name=self.name,
 | 
			
		||||
                shapes=copy.deepcopy(self.shapes, memo),
 | 
			
		||||
                labels=copy.deepcopy(self.labels, memo),
 | 
			
		||||
                subpatterns=copy.deepcopy(self.subpatterns, memo),
 | 
			
		||||
                locked=self.locked)
 | 
			
		||||
        return new
 | 
			
		||||
 | 
			
		||||
    def append(self, other_pattern: 'Pattern') -> 'Pattern':
 | 
			
		||||
@ -363,7 +380,7 @@ class Pattern:
 | 
			
		||||
        :return: A list of (Ni, 2) numpy.ndarrays specifying vertices of the polygons. Each ndarray
 | 
			
		||||
            is of the form [[x0, y0], [x1, y1],...].
 | 
			
		||||
        """
 | 
			
		||||
        pat = copy.deepcopy(self).polygonize().flatten()
 | 
			
		||||
        pat = self.deepcopy().deepunlock().polygonize().flatten()
 | 
			
		||||
        return [shape.vertices + shape.offset for shape in pat.shapes]
 | 
			
		||||
 | 
			
		||||
    def referenced_patterns_by_id(self) -> Dict[int, 'Pattern']:
 | 
			
		||||
@ -564,11 +581,7 @@ class Pattern:
 | 
			
		||||
 | 
			
		||||
        :return: A copy of the current Pattern.
 | 
			
		||||
        """
 | 
			
		||||
        cp = copy.copy(self)
 | 
			
		||||
        cp.shapes = copy.deepcopy(cp.shapes)
 | 
			
		||||
        cp.labels = copy.deepcopy(cp.labels)
 | 
			
		||||
        cp.subpatterns = [copy.copy(subpat) for subpat in cp.subpatterns]
 | 
			
		||||
        return cp
 | 
			
		||||
        return copy.copy(self)
 | 
			
		||||
 | 
			
		||||
    def deepcopy(self) -> 'Pattern':
 | 
			
		||||
        """
 | 
			
		||||
@ -588,6 +601,52 @@ class Pattern:
 | 
			
		||||
                len(self.shapes) == 0 and
 | 
			
		||||
                len(self.labels) == 0)
 | 
			
		||||
 | 
			
		||||
    def lock(self) -> 'Pattern':
 | 
			
		||||
        """
 | 
			
		||||
        Lock the pattern
 | 
			
		||||
 | 
			
		||||
        :return: self
 | 
			
		||||
        """
 | 
			
		||||
        object.__setattr__(self, 'locked', True)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def unlock(self) -> 'Pattern':
 | 
			
		||||
        """
 | 
			
		||||
        Unlock the pattern
 | 
			
		||||
 | 
			
		||||
        :return: self
 | 
			
		||||
        """
 | 
			
		||||
        object.__setattr__(self, 'locked', False)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def deeplock(self) -> 'Pattern':
 | 
			
		||||
        """
 | 
			
		||||
        Recursively lock the pattern, all referenced shapes, subpatterns, and labels
 | 
			
		||||
 | 
			
		||||
        :return: self
 | 
			
		||||
        """
 | 
			
		||||
        self.lock()
 | 
			
		||||
        for ss in self.shapes + self.labels:
 | 
			
		||||
            ss.lock()
 | 
			
		||||
        for sp in self.subpatterns:
 | 
			
		||||
            sp.deeplock()
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def deepunlock(self) -> 'Pattern':
 | 
			
		||||
        """
 | 
			
		||||
        Recursively unlock the pattern, all referenced shapes, subpatterns, and labels
 | 
			
		||||
 | 
			
		||||
        This is dangerous unless you have just performed a deepcopy!
 | 
			
		||||
 | 
			
		||||
        :return: self
 | 
			
		||||
        """
 | 
			
		||||
        self.unlock()
 | 
			
		||||
        for ss in self.shapes + self.labels:
 | 
			
		||||
            ss.unlock()
 | 
			
		||||
        for sp in self.subpatterns:
 | 
			
		||||
            sp.deepunlock()
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def load(filename: str) -> 'Pattern':
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ import copy
 | 
			
		||||
import numpy
 | 
			
		||||
from numpy import pi
 | 
			
		||||
 | 
			
		||||
from .error import PatternError
 | 
			
		||||
from .error import PatternError, PatternLockedError
 | 
			
		||||
from .utils import is_scalar, rotation_matrix_2d, vector2
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,8 @@ class GridRepetition:
 | 
			
		||||
                 '_b_vector',
 | 
			
		||||
                 '_a_count',
 | 
			
		||||
                 '_b_count',
 | 
			
		||||
                 'identifier')
 | 
			
		||||
                 'identifier',
 | 
			
		||||
                 'locked')
 | 
			
		||||
 | 
			
		||||
    pattern: 'Pattern'
 | 
			
		||||
 | 
			
		||||
@ -49,6 +50,7 @@ class GridRepetition:
 | 
			
		||||
    _b_count: int
 | 
			
		||||
 | 
			
		||||
    identifier: Tuple
 | 
			
		||||
    locked: bool
 | 
			
		||||
 | 
			
		||||
    def __init__(self,
 | 
			
		||||
                 pattern: 'Pattern',
 | 
			
		||||
@ -60,7 +62,8 @@ class GridRepetition:
 | 
			
		||||
                 rotation: float = 0.0,
 | 
			
		||||
                 mirrored: List[bool] = None,
 | 
			
		||||
                 dose: float = 1.0,
 | 
			
		||||
                 scale: float = 1.0):
 | 
			
		||||
                 scale: float = 1.0,
 | 
			
		||||
                 locked: bool = False):
 | 
			
		||||
        """
 | 
			
		||||
        :param a_vector: First lattice vector, of the form [x, y].
 | 
			
		||||
            Specifies center-to-center spacing between adjacent elements.
 | 
			
		||||
@ -70,6 +73,7 @@ class GridRepetition:
 | 
			
		||||
            Can be omitted when specifying a 1D array.
 | 
			
		||||
        :param b_count: Number of elements in the b_vector direction.
 | 
			
		||||
            Should be omitted if b_vector was omitted.
 | 
			
		||||
        :param locked: Whether the subpattern is locked after initialization.
 | 
			
		||||
        :raises: InvalidDataError if b_* inputs conflict with each other
 | 
			
		||||
            or a_count < 1.
 | 
			
		||||
        """
 | 
			
		||||
@ -85,6 +89,7 @@ class GridRepetition:
 | 
			
		||||
        if b_count < 1:
 | 
			
		||||
            raise InvalidDataError('Repetition has too-small b_count: '
 | 
			
		||||
                                   '{}'.format(b_count))
 | 
			
		||||
        self.unlock()
 | 
			
		||||
        self.a_vector = a_vector
 | 
			
		||||
        self.b_vector = b_vector
 | 
			
		||||
        self.a_count = a_count
 | 
			
		||||
@ -99,6 +104,12 @@ class GridRepetition:
 | 
			
		||||
        if mirrored is None:
 | 
			
		||||
            mirrored = [False, False]
 | 
			
		||||
        self.mirrored = mirrored
 | 
			
		||||
        self.locked = locked
 | 
			
		||||
 | 
			
		||||
    def __setattr__(self, name, value):
 | 
			
		||||
        if self.locked and name != 'locked':
 | 
			
		||||
            raise PatternLockedError()
 | 
			
		||||
        object.__setattr__(self, name, value)
 | 
			
		||||
 | 
			
		||||
    def  __copy__(self) -> 'GridRepetition':
 | 
			
		||||
        new = GridRepetition(pattern=self.pattern,
 | 
			
		||||
@ -110,7 +121,8 @@ class GridRepetition:
 | 
			
		||||
                             rotation=self.rotation,
 | 
			
		||||
                             dose=self.dose,
 | 
			
		||||
                             scale=self.scale,
 | 
			
		||||
                             mirrored=self.mirrored.copy())
 | 
			
		||||
                             mirrored=self.mirrored.copy(),
 | 
			
		||||
                             locked=self.locked)
 | 
			
		||||
        return new
 | 
			
		||||
 | 
			
		||||
    def  __deepcopy__(self, memo: Dict = None) -> 'GridReptition':
 | 
			
		||||
@ -126,6 +138,9 @@ class GridRepetition:
 | 
			
		||||
 | 
			
		||||
    @offset.setter
 | 
			
		||||
    def offset(self, val: vector2):
 | 
			
		||||
        if self.locked:
 | 
			
		||||
            raise PatternLockedError()
 | 
			
		||||
 | 
			
		||||
        if not isinstance(val, numpy.ndarray):
 | 
			
		||||
            val = numpy.array(val, dtype=float)
 | 
			
		||||
 | 
			
		||||
@ -243,7 +258,7 @@ class GridRepetition:
 | 
			
		||||
        for a in range(self.a_count):
 | 
			
		||||
            for b in range(self.b_count):
 | 
			
		||||
                offset = a * self.a_vector + b * self.b_vector
 | 
			
		||||
                newPat = self.pattern.deepcopy()
 | 
			
		||||
                newPat = self.pattern.deepcopy().deepunlock()
 | 
			
		||||
                newPat.translate_elements(offset)
 | 
			
		||||
                patterns.append(newPat)
 | 
			
		||||
 | 
			
		||||
@ -343,3 +358,42 @@ class GridRepetition:
 | 
			
		||||
        """
 | 
			
		||||
        return copy.deepcopy(self)
 | 
			
		||||
 | 
			
		||||
    def lock(self) -> 'GridRepetition':
 | 
			
		||||
        """
 | 
			
		||||
        Lock the GridRepetition
 | 
			
		||||
 | 
			
		||||
        :return: self
 | 
			
		||||
        """
 | 
			
		||||
        object.__setattr__(self, 'locked', True)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def unlock(self) -> 'GridRepetition':
 | 
			
		||||
        """
 | 
			
		||||
        Unlock the GridRepetition
 | 
			
		||||
 | 
			
		||||
        :return: self
 | 
			
		||||
        """
 | 
			
		||||
        object.__setattr__(self, 'locked', False)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def deeplock(self) -> 'GridRepetition':
 | 
			
		||||
        """
 | 
			
		||||
        Recursively lock the GridRepetition and its contained pattern
 | 
			
		||||
 | 
			
		||||
        :return: self
 | 
			
		||||
        """
 | 
			
		||||
        self.lock()
 | 
			
		||||
        self.pattern.deeplock()
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def deepunlock(self) -> 'GridRepetition':
 | 
			
		||||
        """
 | 
			
		||||
        Recursively unlock the GridRepetition and its contained pattern
 | 
			
		||||
 | 
			
		||||
        This is dangerous unless you have just performed a deepcopy!
 | 
			
		||||
 | 
			
		||||
        :return: self
 | 
			
		||||
        """
 | 
			
		||||
        self.unlock()
 | 
			
		||||
        self.pattern.deepunlock()
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
@ -147,7 +147,9 @@ class Arc(Shape):
 | 
			
		||||
                 rotation: float = 0,
 | 
			
		||||
                 mirrored: Tuple[bool] = (False, False),
 | 
			
		||||
                 layer: int = 0,
 | 
			
		||||
                 dose: float = 1.0):
 | 
			
		||||
                 dose: float = 1.0,
 | 
			
		||||
                 locked: bool = False):
 | 
			
		||||
        self.unlock()
 | 
			
		||||
        self.identifier = ()
 | 
			
		||||
        self.radii = radii
 | 
			
		||||
        self.angles = angles
 | 
			
		||||
@ -159,6 +161,7 @@ class Arc(Shape):
 | 
			
		||||
        self.dose = dose
 | 
			
		||||
        self.poly_num_points = poly_num_points
 | 
			
		||||
        self.poly_max_arclen = poly_max_arclen
 | 
			
		||||
        self.locked = locked
 | 
			
		||||
 | 
			
		||||
    def  __deepcopy__(self, memo: Dict = None) -> 'Arc':
 | 
			
		||||
        memo = {} if memo is None else memo
 | 
			
		||||
 | 
			
		||||
@ -44,7 +44,9 @@ class Circle(Shape):
 | 
			
		||||
                 poly_max_arclen: float = None,
 | 
			
		||||
                 offset: vector2 = (0.0, 0.0),
 | 
			
		||||
                 layer: int = 0,
 | 
			
		||||
                 dose: float = 1.0):
 | 
			
		||||
                 dose: float = 1.0,
 | 
			
		||||
                 locked: bool = False):
 | 
			
		||||
        self.unlock()
 | 
			
		||||
        self.identifier = ()
 | 
			
		||||
        self.offset = numpy.array(offset, dtype=float)
 | 
			
		||||
        self.layer = layer
 | 
			
		||||
@ -52,6 +54,7 @@ class Circle(Shape):
 | 
			
		||||
        self.radius = radius
 | 
			
		||||
        self.poly_num_points = poly_num_points
 | 
			
		||||
        self.poly_max_arclen = poly_max_arclen
 | 
			
		||||
        self.locked = locked
 | 
			
		||||
 | 
			
		||||
    def  __deepcopy__(self, memo: Dict = None) -> 'Circle':
 | 
			
		||||
        memo = {} if memo is None else memo
 | 
			
		||||
 | 
			
		||||
@ -88,7 +88,9 @@ class Ellipse(Shape):
 | 
			
		||||
                 rotation: float = 0,
 | 
			
		||||
                 mirrored: Tuple[bool] = (False, False),
 | 
			
		||||
                 layer: int = 0,
 | 
			
		||||
                 dose: float = 1.0):
 | 
			
		||||
                 dose: float = 1.0,
 | 
			
		||||
                 locked: bool = False):
 | 
			
		||||
        self.unlock()
 | 
			
		||||
        self.identifier = ()
 | 
			
		||||
        self.radii = radii
 | 
			
		||||
        self.offset = offset
 | 
			
		||||
@ -98,6 +100,7 @@ class Ellipse(Shape):
 | 
			
		||||
        self.dose = dose
 | 
			
		||||
        self.poly_num_points = poly_num_points
 | 
			
		||||
        self.poly_max_arclen = poly_max_arclen
 | 
			
		||||
        self.locked = locked
 | 
			
		||||
 | 
			
		||||
    def  __deepcopy__(self, memo: Dict = None) -> 'Ellipse':
 | 
			
		||||
        memo = {} if memo is None else memo
 | 
			
		||||
 | 
			
		||||
@ -151,7 +151,9 @@ class Path(Shape):
 | 
			
		||||
                 mirrored: Tuple[bool] = (False, False),
 | 
			
		||||
                 layer: int = 0,
 | 
			
		||||
                 dose: float = 1.0,
 | 
			
		||||
                 locked: bool = False,
 | 
			
		||||
                 ) -> 'Path':
 | 
			
		||||
        self.unlock()
 | 
			
		||||
        self._cap_extensions = None     # Since .cap setter might access it
 | 
			
		||||
 | 
			
		||||
        self.identifier = ()
 | 
			
		||||
@ -165,6 +167,7 @@ class Path(Shape):
 | 
			
		||||
            self.cap_extensions = cap_extensions
 | 
			
		||||
        self.rotate(rotation)
 | 
			
		||||
        [self.mirror(a) for a, do in enumerate(mirrored) if do]
 | 
			
		||||
        self.locked = locked
 | 
			
		||||
 | 
			
		||||
    def  __deepcopy__(self, memo: Dict = None) -> 'Path':
 | 
			
		||||
        memo = {} if memo is None else memo
 | 
			
		||||
 | 
			
		||||
@ -77,7 +77,9 @@ class Polygon(Shape):
 | 
			
		||||
                 mirrored: Tuple[bool] = (False, False),
 | 
			
		||||
                 layer: int = 0,
 | 
			
		||||
                 dose: float = 1.0,
 | 
			
		||||
                 locked: bool = False,
 | 
			
		||||
                 ):
 | 
			
		||||
        self.unlock()
 | 
			
		||||
        self.identifier = ()
 | 
			
		||||
        self.layer = layer
 | 
			
		||||
        self.dose = dose
 | 
			
		||||
@ -85,6 +87,7 @@ class Polygon(Shape):
 | 
			
		||||
        self.offset = offset
 | 
			
		||||
        self.rotate(rotation)
 | 
			
		||||
        [self.mirror(a) for a, do in enumerate(mirrored) if do]
 | 
			
		||||
        self.locked = locked
 | 
			
		||||
 | 
			
		||||
    def  __deepcopy__(self, memo: Dict = None) -> 'Polygon':
 | 
			
		||||
        memo = {} if memo is None else memo
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@ from abc import ABCMeta, abstractmethod
 | 
			
		||||
import copy
 | 
			
		||||
import numpy
 | 
			
		||||
 | 
			
		||||
from .. import PatternError
 | 
			
		||||
from ..error import PatternError, PatternLockedError
 | 
			
		||||
from ..utils import is_scalar, rotation_matrix_2d, vector2
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -24,13 +24,26 @@ class Shape(metaclass=ABCMeta):
 | 
			
		||||
    """
 | 
			
		||||
    Abstract class specifying functions common to all shapes.
 | 
			
		||||
    """
 | 
			
		||||
    __slots__ = ('_offset', '_layer', '_dose', 'identifier')
 | 
			
		||||
    __slots__ = ('_offset', '_layer', '_dose', 'identifier', 'locked')
 | 
			
		||||
 | 
			
		||||
    _offset: numpy.ndarray      # [x_offset, y_offset]
 | 
			
		||||
    _layer: int or Tuple        # Layer (integer >= 0 or tuple)
 | 
			
		||||
    _dose: float                # Dose
 | 
			
		||||
    identifier: Tuple           # An arbitrary identifier for the shape,
 | 
			
		||||
                                #  usually empty but used by Pattern.flatten()
 | 
			
		||||
    locked: bool                # If True, any changes to the shape will raise a PatternLockedError
 | 
			
		||||
 | 
			
		||||
    def __setattr__(self, name, value):
 | 
			
		||||
        if self.locked and name != 'locked':
 | 
			
		||||
            raise PatternLockedError()
 | 
			
		||||
        object.__setattr__(self, name, value)
 | 
			
		||||
 | 
			
		||||
    def __copy__(self) -> 'Shape':
 | 
			
		||||
        cls = self.__class__
 | 
			
		||||
        new = cls.__new__(cls)
 | 
			
		||||
        for name in Shape.__slots__ + self.__slots__:
 | 
			
		||||
            object.__setattr__(new, name, getattr(self, name))
 | 
			
		||||
        return new
 | 
			
		||||
 | 
			
		||||
    # --- Abstract methods
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
@ -388,3 +401,20 @@ class Shape(metaclass=ABCMeta):
 | 
			
		||||
 | 
			
		||||
        return manhattan_polygons
 | 
			
		||||
 | 
			
		||||
    def lock(self) -> 'Shape':
 | 
			
		||||
        """
 | 
			
		||||
        Lock the Shape
 | 
			
		||||
 | 
			
		||||
        :return: self
 | 
			
		||||
        """
 | 
			
		||||
        object.__setattr__(self, 'locked', True)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def unlock(self) -> 'Shape':
 | 
			
		||||
        """
 | 
			
		||||
        Unlock the Shape
 | 
			
		||||
 | 
			
		||||
        :return: self
 | 
			
		||||
        """
 | 
			
		||||
        object.__setattr__(self, 'locked', False)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
@ -77,7 +77,10 @@ class Text(Shape):
 | 
			
		||||
                 rotation: float = 0.0,
 | 
			
		||||
                 mirrored: Tuple[bool] = (False, False),
 | 
			
		||||
                 layer: int = 0,
 | 
			
		||||
                 dose: float = 1.0):
 | 
			
		||||
                 dose: float = 1.0,
 | 
			
		||||
                 locked: bool = False,
 | 
			
		||||
                 ):
 | 
			
		||||
        self.unlock()
 | 
			
		||||
        self.identifier = ()
 | 
			
		||||
        self.offset = offset
 | 
			
		||||
        self.layer = layer
 | 
			
		||||
@ -87,6 +90,7 @@ class Text(Shape):
 | 
			
		||||
        self.rotation = rotation
 | 
			
		||||
        self.font_path = font_path
 | 
			
		||||
        self.mirrored = mirrored
 | 
			
		||||
        self.locked = locked
 | 
			
		||||
 | 
			
		||||
    def  __deepcopy__(self, memo: Dict = None) -> 'Text':
 | 
			
		||||
        memo = {} if memo is None else memo
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ import copy
 | 
			
		||||
import numpy
 | 
			
		||||
from numpy import pi
 | 
			
		||||
 | 
			
		||||
from .error import PatternError
 | 
			
		||||
from .error import PatternError, PatternLockedError
 | 
			
		||||
from .utils import is_scalar, rotation_matrix_2d, vector2
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,8 @@ class SubPattern:
 | 
			
		||||
    SubPattern provides basic support for nesting Pattern objects within each other, by adding
 | 
			
		||||
     offset, rotation, scaling, and associated methods.
 | 
			
		||||
    """
 | 
			
		||||
    __slots__ = ('pattern', '_offset', '_rotation', '_dose', '_scale', '_mirrored', 'identifier')
 | 
			
		||||
    __slots__ = ('pattern', '_offset', '_rotation', '_dose', '_scale', '_mirrored',
 | 
			
		||||
                 'identifier', 'locked')
 | 
			
		||||
    pattern: 'Pattern'
 | 
			
		||||
    _offset: numpy.ndarray
 | 
			
		||||
    _rotation: float
 | 
			
		||||
@ -29,14 +30,18 @@ class SubPattern:
 | 
			
		||||
    _scale: float
 | 
			
		||||
    _mirrored: List[bool]
 | 
			
		||||
    identifier: Tuple
 | 
			
		||||
    locked: bool
 | 
			
		||||
 | 
			
		||||
    #TODO more documentation?
 | 
			
		||||
    def __init__(self,
 | 
			
		||||
                 pattern: 'Pattern',
 | 
			
		||||
                 offset: vector2 = (0.0, 0.0),
 | 
			
		||||
                 rotation: float = 0.0,
 | 
			
		||||
                 mirrored: List[bool] = None,
 | 
			
		||||
                 dose: float = 1.0,
 | 
			
		||||
                 scale: float = 1.0):
 | 
			
		||||
                 scale: float = 1.0,
 | 
			
		||||
                 locked: bool = False):
 | 
			
		||||
        self.unlock()
 | 
			
		||||
        self.identifier = ()
 | 
			
		||||
        self.pattern = pattern
 | 
			
		||||
        self.offset = offset
 | 
			
		||||
@ -46,6 +51,12 @@ class SubPattern:
 | 
			
		||||
        if mirrored is None:
 | 
			
		||||
            mirrored = [False, False]
 | 
			
		||||
        self.mirrored = mirrored
 | 
			
		||||
        self.locked = locked
 | 
			
		||||
 | 
			
		||||
    def __setattr__(self, name, value):
 | 
			
		||||
        if self.locked and name != 'locked':
 | 
			
		||||
            raise PatternLockedError()
 | 
			
		||||
        object.__setattr__(self, name, value)
 | 
			
		||||
 | 
			
		||||
    def  __copy__(self) -> 'SubPattern':
 | 
			
		||||
        new = SubPattern(pattern=self.pattern,
 | 
			
		||||
@ -53,7 +64,8 @@ class SubPattern:
 | 
			
		||||
                         rotation=self.rotation,
 | 
			
		||||
                         dose=self.dose,
 | 
			
		||||
                         scale=self.scale,
 | 
			
		||||
                         mirrored=self.mirrored.copy())
 | 
			
		||||
                         mirrored=self.mirrored.copy(),
 | 
			
		||||
                         locked=self.locked)
 | 
			
		||||
        return new
 | 
			
		||||
 | 
			
		||||
    def  __deepcopy__(self, memo: Dict = None) -> 'SubPattern':
 | 
			
		||||
@ -130,7 +142,7 @@ class SubPattern:
 | 
			
		||||
         SubPattern's properties.
 | 
			
		||||
        :return: Copy of self.pattern that has been altered to reflect the SubPattern's properties.
 | 
			
		||||
        """
 | 
			
		||||
        pattern = self.pattern.deepcopy()
 | 
			
		||||
        pattern = self.pattern.deepcopy().deepunlock()
 | 
			
		||||
        pattern.scale_by(self.scale)
 | 
			
		||||
        [pattern.mirror(ax) for ax, do in enumerate(self.mirrored) if do]
 | 
			
		||||
        pattern.rotate_around((0.0, 0.0), self.rotation)
 | 
			
		||||
@ -218,3 +230,43 @@ class SubPattern:
 | 
			
		||||
        :return: copy.copy(self)
 | 
			
		||||
        """
 | 
			
		||||
        return copy.deepcopy(self)
 | 
			
		||||
 | 
			
		||||
    def lock(self) -> 'SubPattern':
 | 
			
		||||
        """
 | 
			
		||||
        Lock the SubPattern
 | 
			
		||||
 | 
			
		||||
        :return: self
 | 
			
		||||
        """
 | 
			
		||||
        object.__setattr__(self, 'locked', True)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def unlock(self) -> 'SubPattern':
 | 
			
		||||
        """
 | 
			
		||||
        Unlock the SubPattern
 | 
			
		||||
 | 
			
		||||
        :return: self
 | 
			
		||||
        """
 | 
			
		||||
        object.__setattr__(self, 'locked', False)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def deeplock(self) -> 'SubPattern':
 | 
			
		||||
        """
 | 
			
		||||
        Recursively lock the SubPattern and its contained pattern
 | 
			
		||||
 | 
			
		||||
        :return: self
 | 
			
		||||
        """
 | 
			
		||||
        self.lock()
 | 
			
		||||
        self.pattern.deeplock()
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def deepunlock(self) -> 'SubPattern':
 | 
			
		||||
        """
 | 
			
		||||
        Recursively unlock the SubPattern and its contained pattern
 | 
			
		||||
 | 
			
		||||
        This is dangerous unless you have just performed a deepcopy!
 | 
			
		||||
 | 
			
		||||
        :return: self
 | 
			
		||||
        """
 | 
			
		||||
        self.unlock()
 | 
			
		||||
        self.pattern.deepunlock()
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user