allow locking of all objects
This commit is contained in:
parent
09711116a7
commit
e0db621595
@ -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
|
||||||
|
@ -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')
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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':
|
||||||
"""
|
"""
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user