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…
Reference in New Issue
Block a user