allow locking of all objects

This commit is contained in:
Jan Petykiewicz 2019-12-12 00:38:11 -08:00
parent 09711116a7
commit e0db621595
14 changed files with 290 additions and 36 deletions

View File

@ -25,7 +25,7 @@
import pathlib import pathlib
from .error import PatternError from .error import PatternError, PatternLockedError
from .shapes import Shape from .shapes import Shape
from .label import Label from .label import Label
from .subpattern import SubPattern from .subpattern import SubPattern

View File

@ -7,3 +7,11 @@ class PatternError(Exception):
def __str__(self): def __str__(self):
return repr(self.value) 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')

View File

@ -76,7 +76,7 @@ def write(patterns: Pattern or List[Pattern],
:param library_name: Library name written into the GDSII file. :param library_name: Library name written into the GDSII file.
Default 'masque-gdsii-write'. Default 'masque-gdsii-write'.
:param modify_originals: If True, the original pattern is modified as part of the writing :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. Default False.
:param disambiguate_func: Function which takes a list of patterns and alters them :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 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 disambiguate_func = disambiguate_pattern_names
if not modify_originals: if not modify_originals:
patterns = copy.deepcopy(patterns) patterns = [p.deepcopy().deepunlock() for p in patterns]
# Create library # Create library
lib = gdsii.library.Library(version=600, lib = gdsii.library.Library(version=600,

View File

@ -3,7 +3,7 @@ import copy
import numpy import numpy
from numpy import pi from numpy import pi
from . import PatternError from .error import PatternError, PatternLockedError
from .utils import is_scalar, vector2, rotation_matrix_2d from .utils import is_scalar, vector2, rotation_matrix_2d
@ -12,9 +12,9 @@ __author__ = 'Jan Petykiewicz'
class Label: 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] # [x_offset, y_offset]
_offset: numpy.ndarray _offset: numpy.ndarray
@ -27,6 +27,13 @@ class Label:
# Arbitrary identifier tuple # Arbitrary identifier tuple
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 # ---- Properties
# offset property # offset property
@property @property
@ -78,11 +85,20 @@ class Label:
def __init__(self, def __init__(self,
string: str, string: str,
offset: vector2=(0.0, 0.0), offset: vector2=(0.0, 0.0),
layer: int=0): layer: int=0,
locked: bool = False):
self.unlock()
self.identifier = () self.identifier = ()
self.string = string self.string = string
self.offset = numpy.array(offset, dtype=float) self.offset = numpy.array(offset, dtype=float)
self.layer = layer 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': def __deepcopy__(self, memo: Dict = None) -> 'Label':
memo = {} if memo is None else memo memo = {} if memo is None else memo
@ -134,4 +150,20 @@ class Label:
""" """
return numpy.array([self.offset, self.offset]) 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

View File

@ -17,7 +17,7 @@ from .repetition import GridRepetition
from .shapes import Shape, Polygon from .shapes import Shape, Polygon
from .label import Label from .label import Label
from .utils import rotation_matrix_2d, vector2, normalize_mirror from .utils import rotation_matrix_2d, vector2, normalize_mirror
from .error import PatternError from .error import PatternError, PatternLockedError
__author__ = 'Jan Petykiewicz' __author__ = 'Jan Petykiewicz'
@ -37,17 +37,19 @@ class Pattern:
may reference the same Pattern object. may reference the same Pattern object.
:var name: An identifier for this object. Not necessarily unique. :var name: An identifier for this object. Not necessarily unique.
""" """
__slots__ = ('shapes', 'labels', 'subpatterns', 'name') __slots__ = ('shapes', 'labels', 'subpatterns', 'name', 'locked')
shapes: List[Shape] shapes: List[Shape]
labels: List[Label] labels: List[Label]
subpatterns: List[SubPattern or GridRepetition] subpatterns: List[SubPattern or GridRepetition]
name: str name: str
locked: bool
def __init__(self, def __init__(self,
name: str = '', name: str = '',
shapes: List[Shape] = (), shapes: List[Shape] = (),
labels: List[Label] = (), labels: List[Label] = (),
subpatterns: List[SubPattern] = (), subpatterns: List[SubPattern] = (),
locked: bool = False,
): ):
""" """
Basic init; arguments get assigned to member variables. Basic init; arguments get assigned to member variables.
@ -57,7 +59,9 @@ class Pattern:
:param labels: Initial labels in the Pattern :param labels: Initial labels in the Pattern
:param subpatterns: Initial subpatterns in the Pattern :param subpatterns: Initial subpatterns in the Pattern
:param name: An identifier for the Pattern :param name: An identifier for the Pattern
:param locked: Whether to lock the pattern after construction
""" """
self.unlock()
if isinstance(shapes, list): if isinstance(shapes, list):
self.shapes = shapes self.shapes = shapes
else: else:
@ -74,14 +78,27 @@ class Pattern:
self.subpatterns = list(subpatterns) self.subpatterns = list(subpatterns)
self.name = name 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': def __deepcopy__(self, memo: Dict = None) -> 'Pattern':
memo = {} if memo is None else memo memo = {} if memo is None else memo
new = copy.copy(self) new = Pattern(name=self.name,
new.name = self.name shapes=copy.deepcopy(self.shapes, memo),
new.shapes = copy.deepcopy(self.shapes, memo) labels=copy.deepcopy(self.labels, memo),
new.labels = copy.deepcopy(self.labels, memo) subpatterns=copy.deepcopy(self.subpatterns, memo),
new.subpatterns = copy.deepcopy(self.subpatterns, memo) locked=self.locked)
return new return new
def append(self, other_pattern: 'Pattern') -> 'Pattern': 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 :return: A list of (Ni, 2) numpy.ndarrays specifying vertices of the polygons. Each ndarray
is of the form [[x0, y0], [x1, y1],...]. 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] return [shape.vertices + shape.offset for shape in pat.shapes]
def referenced_patterns_by_id(self) -> Dict[int, 'Pattern']: def referenced_patterns_by_id(self) -> Dict[int, 'Pattern']:
@ -564,11 +581,7 @@ class Pattern:
:return: A copy of the current Pattern. :return: A copy of the current Pattern.
""" """
cp = copy.copy(self) return 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
def deepcopy(self) -> 'Pattern': def deepcopy(self) -> 'Pattern':
""" """
@ -588,6 +601,52 @@ class Pattern:
len(self.shapes) == 0 and len(self.shapes) == 0 and
len(self.labels) == 0) 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 @staticmethod
def load(filename: str) -> 'Pattern': def load(filename: str) -> 'Pattern':
""" """

View File

@ -9,7 +9,7 @@ import copy
import numpy import numpy
from numpy import pi from numpy import pi
from .error import PatternError from .error import PatternError, PatternLockedError
from .utils import is_scalar, rotation_matrix_2d, vector2 from .utils import is_scalar, rotation_matrix_2d, vector2
@ -33,7 +33,8 @@ class GridRepetition:
'_b_vector', '_b_vector',
'_a_count', '_a_count',
'_b_count', '_b_count',
'identifier') 'identifier',
'locked')
pattern: 'Pattern' pattern: 'Pattern'
@ -49,6 +50,7 @@ class GridRepetition:
_b_count: int _b_count: int
identifier: Tuple identifier: Tuple
locked: bool
def __init__(self, def __init__(self,
pattern: 'Pattern', pattern: 'Pattern',
@ -60,7 +62,8 @@ class GridRepetition:
rotation: float = 0.0, rotation: float = 0.0,
mirrored: List[bool] = None, mirrored: List[bool] = None,
dose: float = 1.0, 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]. :param a_vector: First lattice vector, of the form [x, y].
Specifies center-to-center spacing between adjacent elements. Specifies center-to-center spacing between adjacent elements.
@ -70,6 +73,7 @@ class GridRepetition:
Can be omitted when specifying a 1D array. Can be omitted when specifying a 1D array.
:param b_count: Number of elements in the b_vector direction. :param b_count: Number of elements in the b_vector direction.
Should be omitted if b_vector was omitted. 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 :raises: InvalidDataError if b_* inputs conflict with each other
or a_count < 1. or a_count < 1.
""" """
@ -85,6 +89,7 @@ class GridRepetition:
if b_count < 1: if b_count < 1:
raise InvalidDataError('Repetition has too-small b_count: ' raise InvalidDataError('Repetition has too-small b_count: '
'{}'.format(b_count)) '{}'.format(b_count))
self.unlock()
self.a_vector = a_vector self.a_vector = a_vector
self.b_vector = b_vector self.b_vector = b_vector
self.a_count = a_count self.a_count = a_count
@ -99,6 +104,12 @@ class GridRepetition:
if mirrored is None: if mirrored is None:
mirrored = [False, False] mirrored = [False, False]
self.mirrored = mirrored 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': def __copy__(self) -> 'GridRepetition':
new = GridRepetition(pattern=self.pattern, new = GridRepetition(pattern=self.pattern,
@ -110,7 +121,8 @@ class GridRepetition:
rotation=self.rotation, rotation=self.rotation,
dose=self.dose, dose=self.dose,
scale=self.scale, scale=self.scale,
mirrored=self.mirrored.copy()) mirrored=self.mirrored.copy(),
locked=self.locked)
return new return new
def __deepcopy__(self, memo: Dict = None) -> 'GridReptition': def __deepcopy__(self, memo: Dict = None) -> 'GridReptition':
@ -126,6 +138,9 @@ class GridRepetition:
@offset.setter @offset.setter
def offset(self, val: vector2): def offset(self, val: vector2):
if self.locked:
raise PatternLockedError()
if not isinstance(val, numpy.ndarray): if not isinstance(val, numpy.ndarray):
val = numpy.array(val, dtype=float) val = numpy.array(val, dtype=float)
@ -243,7 +258,7 @@ class GridRepetition:
for a in range(self.a_count): for a in range(self.a_count):
for b in range(self.b_count): for b in range(self.b_count):
offset = a * self.a_vector + b * self.b_vector offset = a * self.a_vector + b * self.b_vector
newPat = self.pattern.deepcopy() newPat = self.pattern.deepcopy().deepunlock()
newPat.translate_elements(offset) newPat.translate_elements(offset)
patterns.append(newPat) patterns.append(newPat)
@ -343,3 +358,42 @@ class GridRepetition:
""" """
return copy.deepcopy(self) 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

View File

@ -147,7 +147,9 @@ class Arc(Shape):
rotation: float = 0, rotation: float = 0,
mirrored: Tuple[bool] = (False, False), mirrored: Tuple[bool] = (False, False),
layer: int = 0, layer: int = 0,
dose: float = 1.0): dose: float = 1.0,
locked: bool = False):
self.unlock()
self.identifier = () self.identifier = ()
self.radii = radii self.radii = radii
self.angles = angles self.angles = angles
@ -159,6 +161,7 @@ class Arc(Shape):
self.dose = dose self.dose = dose
self.poly_num_points = poly_num_points self.poly_num_points = poly_num_points
self.poly_max_arclen = poly_max_arclen self.poly_max_arclen = poly_max_arclen
self.locked = locked
def __deepcopy__(self, memo: Dict = None) -> 'Arc': def __deepcopy__(self, memo: Dict = None) -> 'Arc':
memo = {} if memo is None else memo memo = {} if memo is None else memo

View File

@ -44,7 +44,9 @@ class Circle(Shape):
poly_max_arclen: float = None, poly_max_arclen: float = None,
offset: vector2 = (0.0, 0.0), offset: vector2 = (0.0, 0.0),
layer: int = 0, layer: int = 0,
dose: float = 1.0): dose: float = 1.0,
locked: bool = False):
self.unlock()
self.identifier = () self.identifier = ()
self.offset = numpy.array(offset, dtype=float) self.offset = numpy.array(offset, dtype=float)
self.layer = layer self.layer = layer
@ -52,6 +54,7 @@ class Circle(Shape):
self.radius = radius self.radius = radius
self.poly_num_points = poly_num_points self.poly_num_points = poly_num_points
self.poly_max_arclen = poly_max_arclen self.poly_max_arclen = poly_max_arclen
self.locked = locked
def __deepcopy__(self, memo: Dict = None) -> 'Circle': def __deepcopy__(self, memo: Dict = None) -> 'Circle':
memo = {} if memo is None else memo memo = {} if memo is None else memo

View File

@ -88,7 +88,9 @@ class Ellipse(Shape):
rotation: float = 0, rotation: float = 0,
mirrored: Tuple[bool] = (False, False), mirrored: Tuple[bool] = (False, False),
layer: int = 0, layer: int = 0,
dose: float = 1.0): dose: float = 1.0,
locked: bool = False):
self.unlock()
self.identifier = () self.identifier = ()
self.radii = radii self.radii = radii
self.offset = offset self.offset = offset
@ -98,6 +100,7 @@ class Ellipse(Shape):
self.dose = dose self.dose = dose
self.poly_num_points = poly_num_points self.poly_num_points = poly_num_points
self.poly_max_arclen = poly_max_arclen self.poly_max_arclen = poly_max_arclen
self.locked = locked
def __deepcopy__(self, memo: Dict = None) -> 'Ellipse': def __deepcopy__(self, memo: Dict = None) -> 'Ellipse':
memo = {} if memo is None else memo memo = {} if memo is None else memo

View File

@ -151,7 +151,9 @@ class Path(Shape):
mirrored: Tuple[bool] = (False, False), mirrored: Tuple[bool] = (False, False),
layer: int = 0, layer: int = 0,
dose: float = 1.0, dose: float = 1.0,
locked: bool = False,
) -> 'Path': ) -> 'Path':
self.unlock()
self._cap_extensions = None # Since .cap setter might access it self._cap_extensions = None # Since .cap setter might access it
self.identifier = () self.identifier = ()
@ -165,6 +167,7 @@ class Path(Shape):
self.cap_extensions = cap_extensions self.cap_extensions = cap_extensions
self.rotate(rotation) self.rotate(rotation)
[self.mirror(a) for a, do in enumerate(mirrored) if do] [self.mirror(a) for a, do in enumerate(mirrored) if do]
self.locked = locked
def __deepcopy__(self, memo: Dict = None) -> 'Path': def __deepcopy__(self, memo: Dict = None) -> 'Path':
memo = {} if memo is None else memo memo = {} if memo is None else memo

View File

@ -77,7 +77,9 @@ class Polygon(Shape):
mirrored: Tuple[bool] = (False, False), mirrored: Tuple[bool] = (False, False),
layer: int = 0, layer: int = 0,
dose: float = 1.0, dose: float = 1.0,
locked: bool = False,
): ):
self.unlock()
self.identifier = () self.identifier = ()
self.layer = layer self.layer = layer
self.dose = dose self.dose = dose
@ -85,6 +87,7 @@ class Polygon(Shape):
self.offset = offset self.offset = offset
self.rotate(rotation) self.rotate(rotation)
[self.mirror(a) for a, do in enumerate(mirrored) if do] [self.mirror(a) for a, do in enumerate(mirrored) if do]
self.locked = locked
def __deepcopy__(self, memo: Dict = None) -> 'Polygon': def __deepcopy__(self, memo: Dict = None) -> 'Polygon':
memo = {} if memo is None else memo memo = {} if memo is None else memo

View File

@ -3,7 +3,7 @@ from abc import ABCMeta, abstractmethod
import copy import copy
import numpy import numpy
from .. import PatternError from ..error import PatternError, PatternLockedError
from ..utils import is_scalar, rotation_matrix_2d, vector2 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. 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] _offset: numpy.ndarray # [x_offset, y_offset]
_layer: int or Tuple # Layer (integer >= 0 or tuple) _layer: int or Tuple # Layer (integer >= 0 or tuple)
_dose: float # Dose _dose: float # Dose
identifier: Tuple # An arbitrary identifier for the shape, identifier: Tuple # An arbitrary identifier for the shape,
# usually empty but used by Pattern.flatten() # 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 # --- Abstract methods
@abstractmethod @abstractmethod
@ -388,3 +401,20 @@ class Shape(metaclass=ABCMeta):
return manhattan_polygons 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

View File

@ -77,7 +77,10 @@ class Text(Shape):
rotation: float = 0.0, rotation: float = 0.0,
mirrored: Tuple[bool] = (False, False), mirrored: Tuple[bool] = (False, False),
layer: int = 0, layer: int = 0,
dose: float = 1.0): dose: float = 1.0,
locked: bool = False,
):
self.unlock()
self.identifier = () self.identifier = ()
self.offset = offset self.offset = offset
self.layer = layer self.layer = layer
@ -87,6 +90,7 @@ class Text(Shape):
self.rotation = rotation self.rotation = rotation
self.font_path = font_path self.font_path = font_path
self.mirrored = mirrored self.mirrored = mirrored
self.locked = locked
def __deepcopy__(self, memo: Dict = None) -> 'Text': def __deepcopy__(self, memo: Dict = None) -> 'Text':
memo = {} if memo is None else memo memo = {} if memo is None else memo

View File

@ -9,7 +9,7 @@ import copy
import numpy import numpy
from numpy import pi from numpy import pi
from .error import PatternError from .error import PatternError, PatternLockedError
from .utils import is_scalar, rotation_matrix_2d, vector2 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 SubPattern provides basic support for nesting Pattern objects within each other, by adding
offset, rotation, scaling, and associated methods. 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' pattern: 'Pattern'
_offset: numpy.ndarray _offset: numpy.ndarray
_rotation: float _rotation: float
@ -29,14 +30,18 @@ class SubPattern:
_scale: float _scale: float
_mirrored: List[bool] _mirrored: List[bool]
identifier: Tuple identifier: Tuple
locked: bool
#TODO more documentation?
def __init__(self, def __init__(self,
pattern: 'Pattern', pattern: 'Pattern',
offset: vector2 = (0.0, 0.0), offset: vector2 = (0.0, 0.0),
rotation: float = 0.0, rotation: float = 0.0,
mirrored: List[bool] = None, mirrored: List[bool] = None,
dose: float = 1.0, dose: float = 1.0,
scale: float = 1.0): scale: float = 1.0,
locked: bool = False):
self.unlock()
self.identifier = () self.identifier = ()
self.pattern = pattern self.pattern = pattern
self.offset = offset self.offset = offset
@ -46,6 +51,12 @@ class SubPattern:
if mirrored is None: if mirrored is None:
mirrored = [False, False] mirrored = [False, False]
self.mirrored = mirrored 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': def __copy__(self) -> 'SubPattern':
new = SubPattern(pattern=self.pattern, new = SubPattern(pattern=self.pattern,
@ -53,7 +64,8 @@ class SubPattern:
rotation=self.rotation, rotation=self.rotation,
dose=self.dose, dose=self.dose,
scale=self.scale, scale=self.scale,
mirrored=self.mirrored.copy()) mirrored=self.mirrored.copy(),
locked=self.locked)
return new return new
def __deepcopy__(self, memo: Dict = None) -> 'SubPattern': def __deepcopy__(self, memo: Dict = None) -> 'SubPattern':
@ -130,7 +142,7 @@ class SubPattern:
SubPattern's properties. SubPattern's properties.
:return: Copy of self.pattern that has been altered to reflect the 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.scale_by(self.scale)
[pattern.mirror(ax) for ax, do in enumerate(self.mirrored) if do] [pattern.mirror(ax) for ax, do in enumerate(self.mirrored) if do]
pattern.rotate_around((0.0, 0.0), self.rotation) pattern.rotate_around((0.0, 0.0), self.rotation)
@ -218,3 +230,43 @@ class SubPattern:
:return: copy.copy(self) :return: copy.copy(self)
""" """
return copy.deepcopy(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