Remove object locking/unlocking.
- It was *slow*. Often >50% of runtime for large designs. - It didn't catch all corner cases. True immutability would require language-level support. - (minor) It doesn't play nice with type checking via mypy.
This commit is contained in:
parent
fff20b3da9
commit
4d58516049
@ -24,11 +24,10 @@
|
||||
metaclass is used to auto-generate slots based on superclass type annotations.
|
||||
- File I/O submodules are imported by `masque.file` to avoid creating hard dependencies on
|
||||
external file-format reader/writers
|
||||
- Pattern locking/unlocking is quite slow for large hierarchies.
|
||||
|
||||
"""
|
||||
|
||||
from .error import PatternError, PatternLockedError
|
||||
from .error import PatternError
|
||||
from .shapes import Shape
|
||||
from .label import Label
|
||||
from .subpattern import SubPattern
|
||||
|
@ -11,13 +11,6 @@ class PatternError(MasqueError):
|
||||
"""
|
||||
pass
|
||||
|
||||
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')
|
||||
|
||||
|
||||
class LibraryError(MasqueError):
|
||||
"""
|
||||
|
@ -63,7 +63,7 @@ def write(
|
||||
patterns: A Pattern or list of patterns to write to the stream.
|
||||
stream: Stream object to write to.
|
||||
modify_original: If `True`, the original pattern is modified as part of the writing
|
||||
process. Otherwise, a copy is made and `deepunlock()`-ed.
|
||||
process. Otherwise, a copy is made.
|
||||
Default `False`.
|
||||
disambiguate_func: Function which takes a list of patterns and alters them
|
||||
to make their names valid and unique. Default is `disambiguate_pattern_names`.
|
||||
@ -75,7 +75,7 @@ def write(
|
||||
assert(disambiguate_func is not None)
|
||||
|
||||
if not modify_originals:
|
||||
pattern = pattern.deepcopy().deepunlock()
|
||||
pattern = pattern.deepcopy()
|
||||
|
||||
# Get a dict of id(pattern) -> pattern
|
||||
patterns_by_id = pattern.referenced_patterns_by_id()
|
||||
|
@ -94,7 +94,7 @@ def write(
|
||||
library_name: Library name written into the GDSII file.
|
||||
Default 'masque-klamath'.
|
||||
modify_originals: If `True`, the original pattern is modified as part of the writing
|
||||
process. Otherwise, a copy is made and `deepunlock()`-ed.
|
||||
process. Otherwise, a copy is made.
|
||||
Default `False`.
|
||||
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
|
||||
@ -109,7 +109,7 @@ def write(
|
||||
assert(disambiguate_func is not None) # placate mypy
|
||||
|
||||
if not modify_originals:
|
||||
patterns = [p.deepunlock() for p in copy.deepcopy(patterns)]
|
||||
patterns = copy.deepcopy(patterns)
|
||||
|
||||
patterns = [p.wrap_repeated_shapes() for p in patterns]
|
||||
|
||||
|
@ -87,7 +87,7 @@ def build(
|
||||
`fatamorgana.records.LayerName` entries.
|
||||
Default is an empty dict (no names provided).
|
||||
modify_originals: If `True`, the original pattern is modified as part of the writing
|
||||
process. Otherwise, a copy is made and `deepunlock()`-ed.
|
||||
process. Otherwise, a copy is made.
|
||||
Default `False`.
|
||||
disambiguate_func: Function which takes a list of patterns and alters them
|
||||
to make their names valid and unique. Default is `disambiguate_pattern_names`.
|
||||
@ -109,7 +109,7 @@ def build(
|
||||
annotations = {}
|
||||
|
||||
if not modify_originals:
|
||||
patterns = [p.deepunlock() for p in copy.deepcopy(patterns)]
|
||||
patterns = copy.deepcopy(patterns)
|
||||
|
||||
# Create library
|
||||
lib = fatamorgana.OasisLayout(unit=units_per_micron, validation=None)
|
||||
|
@ -95,7 +95,7 @@ def build(
|
||||
library_name: Library name written into the GDSII file.
|
||||
Default 'masque-gdsii-write'.
|
||||
modify_originals: If `True`, the original pattern is modified as part of the writing
|
||||
process. Otherwise, a copy is made and `deepunlock()`-ed.
|
||||
process. Otherwise, a copy is made.
|
||||
Default `False`.
|
||||
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
|
||||
@ -113,7 +113,7 @@ def build(
|
||||
assert(disambiguate_func is not None) # placate mypy
|
||||
|
||||
if not modify_originals:
|
||||
patterns = [p.deepunlock() for p in copy.deepcopy(patterns)]
|
||||
patterns = copy.deepcopy(patterns)
|
||||
|
||||
patterns = [p.wrap_repeated_shapes() for p in patterns]
|
||||
|
||||
|
@ -6,14 +6,13 @@ from numpy.typing import ArrayLike, NDArray
|
||||
|
||||
from .repetition import Repetition
|
||||
from .utils import rotation_matrix_2d, layer_t, AutoSlots, annotations_t
|
||||
from .traits import PositionableImpl, LayerableImpl, Copyable, Pivotable, LockableImpl, RepeatableImpl
|
||||
from .traits import AnnotatableImpl
|
||||
from .traits import PositionableImpl, LayerableImpl, Copyable, Pivotable, RepeatableImpl, AnnotatableImpl
|
||||
|
||||
|
||||
L = TypeVar('L', bound='Label')
|
||||
|
||||
|
||||
class Label(PositionableImpl, LayerableImpl, LockableImpl, RepeatableImpl, AnnotatableImpl,
|
||||
class Label(PositionableImpl, LayerableImpl, RepeatableImpl, AnnotatableImpl,
|
||||
Pivotable, Copyable, metaclass=AutoSlots):
|
||||
"""
|
||||
A text annotation with a position and layer (but no size; it is not drawn)
|
||||
@ -49,33 +48,23 @@ class Label(PositionableImpl, LayerableImpl, LockableImpl, RepeatableImpl, Annot
|
||||
layer: layer_t = 0,
|
||||
repetition: Optional[Repetition] = None,
|
||||
annotations: Optional[annotations_t] = None,
|
||||
locked: bool = False,
|
||||
identifier: Tuple = (),
|
||||
) -> None:
|
||||
LockableImpl.unlock(self)
|
||||
self.identifier = identifier
|
||||
self.string = string
|
||||
self.offset = numpy.array(offset, dtype=float, copy=True)
|
||||
self.layer = layer
|
||||
self.repetition = repetition
|
||||
self.annotations = annotations if annotations is not None else {}
|
||||
self.set_locked(locked)
|
||||
|
||||
def __copy__(self: L) -> L:
|
||||
return type(self)(string=self.string,
|
||||
return type(self)(
|
||||
string=self.string,
|
||||
offset=self.offset.copy(),
|
||||
layer=self.layer,
|
||||
repetition=self.repetition,
|
||||
locked=self.locked,
|
||||
identifier=self.identifier)
|
||||
|
||||
def __deepcopy__(self: L, memo: Dict = None) -> L:
|
||||
memo = {} if memo is None else memo
|
||||
new = copy.copy(self)
|
||||
LockableImpl.unlock(new)
|
||||
new._offset = self._offset.copy()
|
||||
new.set_locked(self.locked)
|
||||
return new
|
||||
identifier=self.identifier,
|
||||
)
|
||||
|
||||
def rotate_around(self: L, pivot: ArrayLike, rotation: float) -> L:
|
||||
"""
|
||||
@ -107,16 +96,5 @@ class Label(PositionableImpl, LayerableImpl, LockableImpl, RepeatableImpl, Annot
|
||||
"""
|
||||
return numpy.array([self.offset, self.offset])
|
||||
|
||||
def lock(self: L) -> L:
|
||||
PositionableImpl._lock(self)
|
||||
LockableImpl.lock(self)
|
||||
return self
|
||||
|
||||
def unlock(self: L) -> L:
|
||||
LockableImpl.unlock(self)
|
||||
PositionableImpl._unlock(self)
|
||||
return self
|
||||
|
||||
def __repr__(self) -> str:
|
||||
locked = ' L' if self.locked else ''
|
||||
return f'<Label "{self.string}" l{self.layer} o{self.offset}{locked}>'
|
||||
return f'<Label "{self.string}" l{self.layer} o{self.offset}>'
|
||||
|
@ -18,9 +18,8 @@ from .subpattern import SubPattern
|
||||
from .shapes import Shape, Polygon
|
||||
from .label import Label
|
||||
from .utils import rotation_matrix_2d, normalize_mirror, AutoSlots, annotations_t
|
||||
from .error import PatternError, PatternLockedError
|
||||
from .traits import LockableImpl, AnnotatableImpl, Scalable, Mirrorable
|
||||
from .traits import Rotatable, Positionable
|
||||
from .error import PatternError
|
||||
from .traits import AnnotatableImpl, Scalable, Mirrorable, Rotatable, Positionable
|
||||
|
||||
|
||||
visitor_function_t = Callable[['Pattern', Tuple['Pattern'], Dict, NDArray[numpy.float64]], 'Pattern']
|
||||
@ -29,7 +28,7 @@ visitor_function_t = Callable[['Pattern', Tuple['Pattern'], Dict, NDArray[numpy.
|
||||
P = TypeVar('P', bound='Pattern')
|
||||
|
||||
|
||||
class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
class Pattern(AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
"""
|
||||
2D layout consisting of some set of shapes, labels, and references to other Pattern objects
|
||||
(via SubPattern). Shapes are assumed to inherit from masque.shapes.Shape or provide equivalent functions.
|
||||
@ -61,7 +60,6 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
labels: Sequence[Label] = (),
|
||||
subpatterns: Sequence[SubPattern] = (),
|
||||
annotations: Optional[annotations_t] = None,
|
||||
locked: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
Basic init; arguments get assigned to member variables.
|
||||
@ -72,9 +70,7 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
labels: Initial labels in the Pattern
|
||||
subpatterns: Initial subpatterns in the Pattern
|
||||
name: An identifier for the Pattern
|
||||
locked: Whether to lock the pattern after construction
|
||||
"""
|
||||
LockableImpl.unlock(self)
|
||||
if isinstance(shapes, list):
|
||||
self.shapes = shapes
|
||||
else:
|
||||
@ -92,15 +88,15 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
|
||||
self.annotations = annotations if annotations is not None else {}
|
||||
self.name = name
|
||||
self.set_locked(locked)
|
||||
|
||||
def __copy__(self, memo: Dict = None) -> 'Pattern':
|
||||
return Pattern(name=self.name,
|
||||
return Pattern(
|
||||
name=self.name,
|
||||
shapes=copy.deepcopy(self.shapes),
|
||||
labels=copy.deepcopy(self.labels),
|
||||
subpatterns=[copy.copy(sp) for sp in self.subpatterns],
|
||||
annotations=copy.deepcopy(self.annotations),
|
||||
locked=self.locked)
|
||||
)
|
||||
|
||||
def __deepcopy__(self, memo: Dict = None) -> 'Pattern':
|
||||
memo = {} if memo is None else memo
|
||||
@ -110,7 +106,7 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
labels=copy.deepcopy(self.labels, memo),
|
||||
subpatterns=copy.deepcopy(self.subpatterns, memo),
|
||||
annotations=copy.deepcopy(self.annotations, memo),
|
||||
locked=self.locked)
|
||||
)
|
||||
return new
|
||||
|
||||
def rename(self: P, name: str) -> P:
|
||||
@ -307,14 +303,13 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
sp_transform = False
|
||||
|
||||
if subpattern.pattern is not None:
|
||||
result = subpattern.pattern.dfs(visit_before=visit_before,
|
||||
subpattern.patern = subpattern.pattern.dfs(
|
||||
visit_before=visit_before,
|
||||
visit_after=visit_after,
|
||||
transform=sp_transform,
|
||||
memo=memo,
|
||||
hierarchy=hierarchy + (self,))
|
||||
if result is not subpattern.pattern:
|
||||
# skip assignment to avoid PatternLockedError unless modified
|
||||
subpattern.pattern = result
|
||||
hierarchy=hierarchy + (self,),
|
||||
)
|
||||
|
||||
if visit_after is not None:
|
||||
pat = visit_after(pat, hierarchy=hierarchy, memo=memo, transform=transform) # type: ignore
|
||||
@ -454,7 +449,7 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
A list of `(Ni, 2)` `numpy.ndarray`s specifying vertices of the polygons. Each ndarray
|
||||
is of the form `[[x0, y0], [x1, y1],...]`.
|
||||
"""
|
||||
pat = self.deepcopy().deepunlock().polygonize().flatten()
|
||||
pat = self.deepcopy().polygonize().flatten()
|
||||
return [shape.vertices + shape.offset for shape in pat.shapes] # type: ignore # mypy can't figure out that shapes are all Polygons now
|
||||
|
||||
@overload
|
||||
@ -872,66 +867,6 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
self.subpatterns.append(SubPattern(*args, **kwargs))
|
||||
return self
|
||||
|
||||
def lock(self: P) -> P:
|
||||
"""
|
||||
Lock the pattern, raising an exception if it is modified.
|
||||
Also see `deeplock()`.
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
if not self.locked:
|
||||
self.shapes = tuple(self.shapes)
|
||||
self.labels = tuple(self.labels)
|
||||
self.subpatterns = tuple(self.subpatterns)
|
||||
LockableImpl.lock(self)
|
||||
return self
|
||||
|
||||
def unlock(self: P) -> P:
|
||||
"""
|
||||
Unlock the pattern
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
if self.locked:
|
||||
LockableImpl.unlock(self)
|
||||
self.shapes = list(self.shapes)
|
||||
self.labels = list(self.labels)
|
||||
self.subpatterns = list(self.subpatterns)
|
||||
return self
|
||||
|
||||
def deeplock(self: P) -> P:
|
||||
"""
|
||||
Recursively lock the pattern, all referenced shapes, subpatterns, and labels.
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
self.lock()
|
||||
for ss in chain(self.shapes, self.labels):
|
||||
ss.lock() # type: ignore # mypy struggles with multiple inheritance :(
|
||||
for sp in self.subpatterns:
|
||||
sp.deeplock()
|
||||
return self
|
||||
|
||||
def deepunlock(self: P) -> P:
|
||||
"""
|
||||
Recursively unlock the pattern, all referenced shapes, subpatterns, and labels.
|
||||
|
||||
This is dangerous unless you have just performed a deepcopy, since anything
|
||||
you change will be changed everywhere it is referenced!
|
||||
|
||||
Return:
|
||||
self
|
||||
"""
|
||||
self.unlock()
|
||||
for ss in chain(self.shapes, self.labels):
|
||||
ss.unlock() # type: ignore # mypy struggles with multiple inheritance :(
|
||||
for sp in self.subpatterns:
|
||||
sp.deepunlock()
|
||||
return self
|
||||
|
||||
@staticmethod
|
||||
def load(filename: str) -> 'Pattern':
|
||||
"""
|
||||
@ -1046,5 +981,4 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
return toplevel
|
||||
|
||||
def __repr__(self) -> str:
|
||||
locked = ' L' if self.locked else ''
|
||||
return (f'<Pattern "{self.name}": sh{len(self.shapes)} sp{len(self.subpatterns)} la{len(self.labels)}{locked}>')
|
||||
return (f'<Pattern "{self.name}": sh{len(self.shapes)} sp{len(self.subpatterns)} la{len(self.labels)}>')
|
||||
|
@ -12,7 +12,7 @@ from numpy.typing import ArrayLike, NDArray
|
||||
|
||||
from .error import PatternError
|
||||
from .utils import rotation_matrix_2d, AutoSlots
|
||||
from .traits import LockableImpl, Copyable, Scalable, Rotatable, Mirrorable
|
||||
from .traits import Copyable, Scalable, Rotatable, Mirrorable
|
||||
|
||||
|
||||
class Repetition(Copyable, Rotatable, Mirrorable, Scalable, metaclass=ABCMeta):
|
||||
@ -30,7 +30,7 @@ class Repetition(Copyable, Rotatable, Mirrorable, Scalable, metaclass=ABCMeta):
|
||||
pass
|
||||
|
||||
|
||||
class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
||||
class Grid(Repetition, metaclass=AutoSlots):
|
||||
"""
|
||||
`Grid` describes a 2D grid formed by two basis vectors and two 'counts' (sizes).
|
||||
|
||||
@ -67,7 +67,6 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
||||
a_count: int,
|
||||
b_vector: Optional[ArrayLike] = None,
|
||||
b_count: Optional[int] = 1,
|
||||
locked: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
Args:
|
||||
@ -79,7 +78,6 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
||||
Can be omitted when specifying a 1D array.
|
||||
b_count: Number of elements in the `b_vector` direction.
|
||||
Should be omitted if `b_vector` was omitted.
|
||||
locked: Whether the `Grid` is locked after initialization.
|
||||
|
||||
Raises:
|
||||
PatternError if `b_*` inputs conflict with each other
|
||||
@ -99,12 +97,10 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
||||
if b_count < 1:
|
||||
raise PatternError(f'Repetition has too-small b_count: {b_count}')
|
||||
|
||||
object.__setattr__(self, 'locked', False)
|
||||
self.a_vector = a_vector # type: ignore # setter handles type conversion
|
||||
self.b_vector = b_vector # type: ignore # setter handles type conversion
|
||||
self.a_count = a_count
|
||||
self.b_count = b_count
|
||||
self.locked = locked
|
||||
|
||||
@classmethod
|
||||
def aligned(
|
||||
@ -129,18 +125,12 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
||||
return cls(a_vector=(x, 0), b_vector=(0, y), a_count=x_count, b_count=y_count)
|
||||
|
||||
def __copy__(self) -> 'Grid':
|
||||
new = Grid(a_vector=self.a_vector.copy(),
|
||||
new = Grid(
|
||||
a_vector=self.a_vector.copy(),
|
||||
b_vector=copy.copy(self.b_vector),
|
||||
a_count=self.a_count,
|
||||
b_count=self.b_count,
|
||||
locked=self.locked)
|
||||
return new
|
||||
|
||||
def __deepcopy__(self, memo: Dict = None) -> 'Grid':
|
||||
memo = {} if memo is None else memo
|
||||
new = copy.copy(self)
|
||||
LocakbleImpl.unlock(new)
|
||||
new.locked = self.locked
|
||||
)
|
||||
return new
|
||||
|
||||
# a_vector property
|
||||
@ -264,36 +254,9 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
||||
self.b_vector *= c
|
||||
return self
|
||||
|
||||
def lock(self) -> 'Grid':
|
||||
"""
|
||||
Lock the `Grid`, disallowing changes.
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
self.a_vector.flags.writeable = False
|
||||
if self.b_vector is not None:
|
||||
self.b_vector.flags.writeable = False
|
||||
LockableImpl.lock(self)
|
||||
return self
|
||||
|
||||
def unlock(self) -> 'Grid':
|
||||
"""
|
||||
Unlock the `Grid`
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
self.a_vector.flags.writeable = True
|
||||
if self.b_vector is not None:
|
||||
self.b_vector.flags.writeable = True
|
||||
LockableImpl.unlock(self)
|
||||
return self
|
||||
|
||||
def __repr__(self) -> str:
|
||||
locked = ' L' if self.locked else ''
|
||||
bv = f', {self.b_vector}' if self.b_vector is not None else ''
|
||||
return (f'<Grid {self.a_count}x{self.b_count} ({self.a_vector}{bv}){locked}>')
|
||||
return (f'<Grid {self.a_count}x{self.b_count} ({self.a_vector}{bv})>')
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if not isinstance(other, type(self)):
|
||||
@ -308,12 +271,10 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
||||
return False
|
||||
if any(self.b_vector[ii] != other.b_vector[ii] for ii in range(2)):
|
||||
return False
|
||||
if self.locked != other.locked:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class Arbitrary(LockableImpl, Repetition, metaclass=AutoSlots):
|
||||
class Arbitrary(Repetition, metaclass=AutoSlots):
|
||||
"""
|
||||
`Arbitrary` is a simple list of (absolute) displacements for instances.
|
||||
|
||||
@ -342,48 +303,19 @@ class Arbitrary(LockableImpl, Repetition, metaclass=AutoSlots):
|
||||
def __init__(
|
||||
self,
|
||||
displacements: ArrayLike,
|
||||
locked: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
Args:
|
||||
displacements: List of vectors (Nx2 ndarray) specifying displacements.
|
||||
locked: Whether the object is locked after initialization.
|
||||
"""
|
||||
object.__setattr__(self, 'locked', False)
|
||||
self.displacements = displacements
|
||||
self.locked = locked
|
||||
|
||||
def lock(self) -> 'Arbitrary':
|
||||
"""
|
||||
Lock the object, disallowing changes.
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
self._displacements.flags.writeable = False
|
||||
LockableImpl.lock(self)
|
||||
return self
|
||||
|
||||
def unlock(self) -> 'Arbitrary':
|
||||
"""
|
||||
Unlock the object
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
self._displacements.flags.writeable = True
|
||||
LockableImpl.unlock(self)
|
||||
return self
|
||||
|
||||
def __repr__(self) -> str:
|
||||
locked = ' L' if self.locked else ''
|
||||
return (f'<Arbitrary {len(self.displacements)}pts {locked}>')
|
||||
return (f'<Arbitrary {len(self.displacements)}pts>')
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if not isinstance(other, type(self)):
|
||||
return False
|
||||
if self.locked != other.locked:
|
||||
return False
|
||||
return numpy.array_equal(self.displacements, other.displacements)
|
||||
|
||||
def rotate(self, rotation: float) -> 'Arbitrary':
|
||||
|
@ -10,7 +10,6 @@ from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS
|
||||
from .. import PatternError
|
||||
from ..repetition import Repetition
|
||||
from ..utils import is_scalar, layer_t, AutoSlots, annotations_t
|
||||
from ..traits import LockableImpl
|
||||
|
||||
|
||||
class Arc(Shape, metaclass=AutoSlots):
|
||||
@ -166,10 +165,8 @@ class Arc(Shape, metaclass=AutoSlots):
|
||||
dose: float = 1.0,
|
||||
repetition: Optional[Repetition] = None,
|
||||
annotations: Optional[annotations_t] = None,
|
||||
locked: bool = False,
|
||||
raw: bool = False,
|
||||
) -> None:
|
||||
LockableImpl.unlock(self)
|
||||
self.identifier = ()
|
||||
if raw:
|
||||
assert(isinstance(radii, numpy.ndarray))
|
||||
@ -197,18 +194,6 @@ class Arc(Shape, metaclass=AutoSlots):
|
||||
self.poly_num_points = poly_num_points
|
||||
self.poly_max_arclen = poly_max_arclen
|
||||
[self.mirror(a) for a, do in enumerate(mirrored) if do]
|
||||
self.set_locked(locked)
|
||||
|
||||
def __deepcopy__(self, memo: Dict = None) -> 'Arc':
|
||||
memo = {} if memo is None else memo
|
||||
new = copy.copy(self)
|
||||
Shape.unlock(new)
|
||||
new._offset = self._offset.copy()
|
||||
new._radii = self._radii.copy()
|
||||
new._angles = self._angles.copy()
|
||||
new._annotations = copy.deepcopy(self._annotations)
|
||||
new.set_locked(self.locked)
|
||||
return new
|
||||
|
||||
def to_polygons(
|
||||
self,
|
||||
@ -429,21 +414,8 @@ class Arc(Shape, metaclass=AutoSlots):
|
||||
a.append((a0, a1))
|
||||
return numpy.array(a)
|
||||
|
||||
def lock(self) -> 'Arc':
|
||||
self.radii.flags.writeable = False
|
||||
self.angles.flags.writeable = False
|
||||
Shape.lock(self)
|
||||
return self
|
||||
|
||||
def unlock(self) -> 'Arc':
|
||||
Shape.unlock(self)
|
||||
self.radii.flags.writeable = True
|
||||
self.angles.flags.writeable = True
|
||||
return self
|
||||
|
||||
def __repr__(self) -> str:
|
||||
angles = f' a°{numpy.rad2deg(self.angles)}'
|
||||
rotation = f' r°{numpy.rad2deg(self.rotation):g}' if self.rotation != 0 else ''
|
||||
dose = f' d{self.dose:g}' if self.dose != 1 else ''
|
||||
locked = ' L' if self.locked else ''
|
||||
return f'<Arc l{self.layer} o{self.offset} r{self.radii}{angles} w{self.width:g}{rotation}{dose}{locked}>'
|
||||
return f'<Arc l{self.layer} o{self.offset} r{self.radii}{angles} w{self.width:g}{rotation}{dose}>'
|
||||
|
@ -9,7 +9,6 @@ from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS
|
||||
from .. import PatternError
|
||||
from ..repetition import Repetition
|
||||
from ..utils import is_scalar, layer_t, AutoSlots, annotations_t
|
||||
from ..traits import LockableImpl
|
||||
|
||||
|
||||
class Circle(Shape, metaclass=AutoSlots):
|
||||
@ -54,10 +53,8 @@ class Circle(Shape, metaclass=AutoSlots):
|
||||
dose: float = 1.0,
|
||||
repetition: Optional[Repetition] = None,
|
||||
annotations: Optional[annotations_t] = None,
|
||||
locked: bool = False,
|
||||
raw: bool = False,
|
||||
) -> None:
|
||||
LockableImpl.unlock(self)
|
||||
self.identifier = ()
|
||||
if raw:
|
||||
assert(isinstance(offset, numpy.ndarray))
|
||||
@ -76,16 +73,6 @@ class Circle(Shape, metaclass=AutoSlots):
|
||||
self.dose = dose
|
||||
self.poly_num_points = poly_num_points
|
||||
self.poly_max_arclen = poly_max_arclen
|
||||
self.set_locked(locked)
|
||||
|
||||
def __deepcopy__(self, memo: Dict = None) -> 'Circle':
|
||||
memo = {} if memo is None else memo
|
||||
new = copy.copy(self)
|
||||
Shape.unlock(new)
|
||||
new._offset = self._offset.copy()
|
||||
new._annotations = copy.deepcopy(self._annotations)
|
||||
new.set_locked(self.locked)
|
||||
return new
|
||||
|
||||
def to_polygons(
|
||||
self,
|
||||
@ -138,5 +125,4 @@ class Circle(Shape, metaclass=AutoSlots):
|
||||
|
||||
def __repr__(self) -> str:
|
||||
dose = f' d{self.dose:g}' if self.dose != 1 else ''
|
||||
locked = ' L' if self.locked else ''
|
||||
return f'<Circle l{self.layer} o{self.offset} r{self.radius:g}{dose}{locked}>'
|
||||
return f'<Circle l{self.layer} o{self.offset} r{self.radius:g}{dose}>'
|
||||
|
@ -10,7 +10,6 @@ from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS
|
||||
from .. import PatternError
|
||||
from ..repetition import Repetition
|
||||
from ..utils import is_scalar, rotation_matrix_2d, layer_t, AutoSlots, annotations_t
|
||||
from ..traits import LockableImpl
|
||||
|
||||
|
||||
class Ellipse(Shape, metaclass=AutoSlots):
|
||||
@ -101,10 +100,8 @@ class Ellipse(Shape, metaclass=AutoSlots):
|
||||
dose: float = 1.0,
|
||||
repetition: Optional[Repetition] = None,
|
||||
annotations: Optional[annotations_t] = None,
|
||||
locked: bool = False,
|
||||
raw: bool = False,
|
||||
) -> None:
|
||||
LockableImpl.unlock(self)
|
||||
self.identifier = ()
|
||||
if raw:
|
||||
assert(isinstance(radii, numpy.ndarray))
|
||||
@ -127,17 +124,6 @@ class Ellipse(Shape, metaclass=AutoSlots):
|
||||
[self.mirror(a) for a, do in enumerate(mirrored) if do]
|
||||
self.poly_num_points = poly_num_points
|
||||
self.poly_max_arclen = poly_max_arclen
|
||||
self.set_locked(locked)
|
||||
|
||||
def __deepcopy__(self, memo: Dict = None) -> 'Ellipse':
|
||||
memo = {} if memo is None else memo
|
||||
new = copy.copy(self)
|
||||
Shape.unlock(new)
|
||||
new._offset = self._offset.copy()
|
||||
new._radii = self._radii.copy()
|
||||
new._annotations = copy.deepcopy(self._annotations)
|
||||
new.set_locked(self.locked)
|
||||
return new
|
||||
|
||||
def to_polygons(
|
||||
self,
|
||||
@ -209,18 +195,7 @@ class Ellipse(Shape, metaclass=AutoSlots):
|
||||
(self.offset, scale / norm_value, angle, False, self.dose),
|
||||
lambda: Ellipse(radii=radii * norm_value, layer=self.layer))
|
||||
|
||||
def lock(self) -> 'Ellipse':
|
||||
self.radii.flags.writeable = False
|
||||
Shape.lock(self)
|
||||
return self
|
||||
|
||||
def unlock(self) -> 'Ellipse':
|
||||
Shape.unlock(self)
|
||||
self.radii.flags.writeable = True
|
||||
return self
|
||||
|
||||
def __repr__(self) -> str:
|
||||
rotation = f' r{self.rotation*180/pi:g}' if self.rotation != 0 else ''
|
||||
dose = f' d{self.dose:g}' if self.dose != 1 else ''
|
||||
locked = ' L' if self.locked else ''
|
||||
return f'<Ellipse l{self.layer} o{self.offset} r{self.radii}{rotation}{dose}{locked}>'
|
||||
return f'<Ellipse l{self.layer} o{self.offset} r{self.radii}{rotation}{dose}>'
|
||||
|
@ -11,7 +11,6 @@ from .. import PatternError
|
||||
from ..repetition import Repetition
|
||||
from ..utils import is_scalar, rotation_matrix_2d, layer_t, AutoSlots
|
||||
from ..utils import remove_colinear_vertices, remove_duplicate_vertices, annotations_t
|
||||
from ..traits import LockableImpl
|
||||
|
||||
|
||||
class PathCap(Enum):
|
||||
@ -155,10 +154,8 @@ class Path(Shape, metaclass=AutoSlots):
|
||||
dose: float = 1.0,
|
||||
repetition: Optional[Repetition] = None,
|
||||
annotations: Optional[annotations_t] = None,
|
||||
locked: bool = False,
|
||||
raw: bool = False,
|
||||
) -> None:
|
||||
LockableImpl.unlock(self)
|
||||
self._cap_extensions = None # Since .cap setter might access it
|
||||
|
||||
self.identifier = ()
|
||||
@ -187,18 +184,15 @@ class Path(Shape, metaclass=AutoSlots):
|
||||
self.cap_extensions = cap_extensions
|
||||
self.rotate(rotation)
|
||||
[self.mirror(a) for a, do in enumerate(mirrored) if do]
|
||||
self.set_locked(locked)
|
||||
|
||||
def __deepcopy__(self, memo: Dict = None) -> 'Path':
|
||||
memo = {} if memo is None else memo
|
||||
new = copy.copy(self)
|
||||
Shape.unlock(new)
|
||||
new._offset = self._offset.copy()
|
||||
new._vertices = self._vertices.copy()
|
||||
new._cap = copy.deepcopy(self._cap, memo)
|
||||
new._cap_extensions = copy.deepcopy(self._cap_extensions, memo)
|
||||
new._annotations = copy.deepcopy(self._annotations)
|
||||
new.set_locked(self.locked)
|
||||
return new
|
||||
|
||||
@staticmethod
|
||||
@ -424,22 +418,7 @@ class Path(Shape, metaclass=AutoSlots):
|
||||
extensions = numpy.zeros(2)
|
||||
return extensions
|
||||
|
||||
def lock(self) -> 'Path':
|
||||
self.vertices.flags.writeable = False
|
||||
if self.cap_extensions is not None:
|
||||
self.cap_extensions.flags.writeable = False
|
||||
Shape.lock(self)
|
||||
return self
|
||||
|
||||
def unlock(self) -> 'Path':
|
||||
Shape.unlock(self)
|
||||
self.vertices.flags.writeable = True
|
||||
if self.cap_extensions is not None:
|
||||
self.cap_extensions.flags.writeable = True
|
||||
return self
|
||||
|
||||
def __repr__(self) -> str:
|
||||
centroid = self.offset + self.vertices.mean(axis=0)
|
||||
dose = f' d{self.dose:g}' if self.dose != 1 else ''
|
||||
locked = ' L' if self.locked else ''
|
||||
return f'<Path l{self.layer} centroid {centroid} v{len(self.vertices)} w{self.width} c{self.cap}{dose}{locked}>'
|
||||
return f'<Path l{self.layer} centroid {centroid} v{len(self.vertices)} w{self.width} c{self.cap}{dose}>'
|
||||
|
@ -10,7 +10,6 @@ from .. import PatternError
|
||||
from ..repetition import Repetition
|
||||
from ..utils import is_scalar, rotation_matrix_2d, layer_t, AutoSlots
|
||||
from ..utils import remove_colinear_vertices, remove_duplicate_vertices, annotations_t
|
||||
from ..traits import LockableImpl
|
||||
|
||||
|
||||
class Polygon(Shape, metaclass=AutoSlots):
|
||||
@ -83,10 +82,8 @@ class Polygon(Shape, metaclass=AutoSlots):
|
||||
dose: float = 1.0,
|
||||
repetition: Optional[Repetition] = None,
|
||||
annotations: Optional[annotations_t] = None,
|
||||
locked: bool = False,
|
||||
raw: bool = False,
|
||||
) -> None:
|
||||
LockableImpl.unlock(self)
|
||||
self.identifier = ()
|
||||
if raw:
|
||||
assert(isinstance(vertices, numpy.ndarray))
|
||||
@ -106,17 +103,6 @@ class Polygon(Shape, metaclass=AutoSlots):
|
||||
self.dose = dose
|
||||
self.rotate(rotation)
|
||||
[self.mirror(a) for a, do in enumerate(mirrored) if do]
|
||||
self.set_locked(locked)
|
||||
|
||||
def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Polygon':
|
||||
memo = {} if memo is None else memo
|
||||
new = copy.copy(self)
|
||||
Shape.unlock(new)
|
||||
new._offset = self._offset.copy()
|
||||
new._vertices = self._vertices.copy()
|
||||
new._annotations = copy.deepcopy(self._annotations)
|
||||
new.set_locked(self.locked)
|
||||
return new
|
||||
|
||||
@staticmethod
|
||||
def square(
|
||||
@ -430,18 +416,7 @@ class Polygon(Shape, metaclass=AutoSlots):
|
||||
self.vertices = remove_colinear_vertices(self.vertices, closed_path=True)
|
||||
return self
|
||||
|
||||
def lock(self) -> 'Polygon':
|
||||
self.vertices.flags.writeable = False
|
||||
Shape.lock(self)
|
||||
return self
|
||||
|
||||
def unlock(self) -> 'Polygon':
|
||||
Shape.unlock(self)
|
||||
self.vertices.flags.writeable = True
|
||||
return self
|
||||
|
||||
def __repr__(self) -> str:
|
||||
centroid = self.offset + self.vertices.mean(axis=0)
|
||||
dose = f' d{self.dose:g}' if self.dose != 1 else ''
|
||||
locked = ' L' if self.locked else ''
|
||||
return f'<Polygon l{self.layer} centroid {centroid} v{len(self.vertices)}{dose}{locked}>'
|
||||
return f'<Polygon l{self.layer} centroid {centroid} v{len(self.vertices)}{dose}>'
|
||||
|
@ -4,10 +4,11 @@ from abc import ABCMeta, abstractmethod
|
||||
import numpy
|
||||
from numpy.typing import NDArray, ArrayLike
|
||||
|
||||
from ..traits import (PositionableImpl, LayerableImpl, DoseableImpl,
|
||||
from ..traits import (
|
||||
PositionableImpl, LayerableImpl, DoseableImpl,
|
||||
Rotatable, Mirrorable, Copyable, Scalable,
|
||||
PivotableImpl, LockableImpl, RepeatableImpl,
|
||||
AnnotatableImpl)
|
||||
PivotableImpl, RepeatableImpl, AnnotatableImpl,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import Polygon
|
||||
@ -27,7 +28,7 @@ T = TypeVar('T', bound='Shape')
|
||||
|
||||
|
||||
class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable, Copyable, Scalable,
|
||||
PivotableImpl, RepeatableImpl, LockableImpl, AnnotatableImpl, metaclass=ABCMeta):
|
||||
PivotableImpl, RepeatableImpl, AnnotatableImpl, metaclass=ABCMeta):
|
||||
"""
|
||||
Abstract class specifying functions common to all shapes.
|
||||
"""
|
||||
@ -36,13 +37,6 @@ class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable
|
||||
identifier: Tuple
|
||||
""" An arbitrary identifier for the shape, usually empty but used by `Pattern.flatten()` """
|
||||
|
||||
def __copy__(self) -> 'Shape':
|
||||
cls = self.__class__
|
||||
new = cls.__new__(cls)
|
||||
for name in self.__slots__: # type: str
|
||||
object.__setattr__(new, name, getattr(self, name))
|
||||
return new
|
||||
|
||||
'''
|
||||
--- Abstract methods
|
||||
'''
|
||||
@ -303,13 +297,3 @@ class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable
|
||||
dose=self.dose))
|
||||
|
||||
return manhattan_polygons
|
||||
|
||||
def lock(self: T) -> T:
|
||||
PositionableImpl._lock(self)
|
||||
LockableImpl.lock(self)
|
||||
return self
|
||||
|
||||
def unlock(self: T) -> T:
|
||||
LockableImpl.unlock(self)
|
||||
PositionableImpl._unlock(self)
|
||||
return self
|
||||
|
@ -11,7 +11,6 @@ from ..repetition import Repetition
|
||||
from ..traits import RotatableImpl
|
||||
from ..utils import is_scalar, get_bit, normalize_mirror, layer_t, AutoSlots
|
||||
from ..utils import annotations_t
|
||||
from ..traits import LockableImpl
|
||||
|
||||
# Loaded on use:
|
||||
# from freetype import Face
|
||||
@ -74,10 +73,8 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
|
||||
dose: float = 1.0,
|
||||
repetition: Optional[Repetition] = None,
|
||||
annotations: Optional[annotations_t] = None,
|
||||
locked: bool = False,
|
||||
raw: bool = False,
|
||||
) -> None:
|
||||
LockableImpl.unlock(self)
|
||||
self.identifier = ()
|
||||
if raw:
|
||||
assert(isinstance(offset, numpy.ndarray))
|
||||
@ -102,17 +99,6 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
|
||||
self.repetition = repetition
|
||||
self.annotations = annotations if annotations is not None else {}
|
||||
self.font_path = font_path
|
||||
self.set_locked(locked)
|
||||
|
||||
def __deepcopy__(self, memo: Dict = None) -> 'Text':
|
||||
memo = {} if memo is None else memo
|
||||
new = copy.copy(self)
|
||||
Shape.unlock(new)
|
||||
new._offset = self._offset.copy()
|
||||
new._mirrored = copy.deepcopy(self._mirrored, memo)
|
||||
new._annotations = copy.deepcopy(self._annotations)
|
||||
new.set_locked(self.locked)
|
||||
return new
|
||||
|
||||
def to_polygons(
|
||||
self,
|
||||
@ -259,19 +245,8 @@ def get_char_as_polygons(
|
||||
|
||||
return polygons, advance
|
||||
|
||||
def lock(self) -> 'Text':
|
||||
self.mirrored.flags.writeable = False
|
||||
Shape.lock(self)
|
||||
return self
|
||||
|
||||
def unlock(self) -> 'Text':
|
||||
Shape.unlock(self)
|
||||
self.mirrored.flags.writeable = True
|
||||
return self
|
||||
|
||||
def __repr__(self) -> str:
|
||||
rotation = f' r°{self.rotation*180/pi:g}' if self.rotation != 0 else ''
|
||||
dose = f' d{self.dose:g}' if self.dose != 1 else ''
|
||||
locked = ' L' if self.locked else ''
|
||||
mirrored = ' m{:d}{:d}'.format(*self.mirrored) if self.mirrored.any() else ''
|
||||
return f'<TextShape "{self.string}" l{self.layer} o{self.offset} h{self.height:g}{rotation}{mirrored}{dose}{locked}>'
|
||||
return f'<TextShape "{self.string}" l{self.layer} o{self.offset} h{self.height:g}{rotation}{mirrored}{dose}>'
|
||||
|
@ -14,9 +14,10 @@ from numpy.typing import NDArray, ArrayLike
|
||||
from .error import PatternError
|
||||
from .utils import is_scalar, AutoSlots, annotations_t
|
||||
from .repetition import Repetition
|
||||
from .traits import (PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl,
|
||||
Mirrorable, PivotableImpl, Copyable, LockableImpl, RepeatableImpl,
|
||||
AnnotatableImpl)
|
||||
from .traits import (
|
||||
PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl,
|
||||
Mirrorable, PivotableImpl, Copyable, RepeatableImpl, AnnotatableImpl
|
||||
)
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -27,7 +28,7 @@ S = TypeVar('S', bound='SubPattern')
|
||||
|
||||
|
||||
class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mirrorable,
|
||||
PivotableImpl, Copyable, RepeatableImpl, LockableImpl, AnnotatableImpl,
|
||||
PivotableImpl, Copyable, RepeatableImpl, AnnotatableImpl,
|
||||
metaclass=AutoSlots):
|
||||
"""
|
||||
SubPattern provides basic support for nesting Pattern objects within each other, by adding
|
||||
@ -58,7 +59,6 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
||||
scale: float = 1.0,
|
||||
repetition: Optional[Repetition] = None,
|
||||
annotations: Optional[annotations_t] = None,
|
||||
locked: bool = False,
|
||||
identifier: Tuple[Any, ...] = (),
|
||||
) -> None:
|
||||
"""
|
||||
@ -70,10 +70,8 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
||||
dose: Scaling factor applied to the dose.
|
||||
scale: Scaling factor applied to the pattern's geometry.
|
||||
repetition: TODO
|
||||
locked: Whether the `SubPattern` is locked after initialization.
|
||||
identifier: Arbitrary tuple, used internally by some `masque` functions.
|
||||
"""
|
||||
LockableImpl.unlock(self)
|
||||
self.identifier = identifier
|
||||
self.pattern = pattern
|
||||
self.offset = offset
|
||||
@ -85,10 +83,10 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
||||
self.mirrored = mirrored
|
||||
self.repetition = repetition
|
||||
self.annotations = annotations if annotations is not None else {}
|
||||
self.set_locked(locked)
|
||||
|
||||
def __copy__(self) -> 'SubPattern':
|
||||
new = SubPattern(pattern=self.pattern,
|
||||
new = SubPattern(
|
||||
pattern=self.pattern,
|
||||
offset=self.offset.copy(),
|
||||
rotation=self.rotation,
|
||||
dose=self.dose,
|
||||
@ -96,17 +94,7 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
||||
mirrored=self.mirrored.copy(),
|
||||
repetition=copy.deepcopy(self.repetition),
|
||||
annotations=copy.deepcopy(self.annotations),
|
||||
locked=self.locked)
|
||||
return new
|
||||
|
||||
def __deepcopy__(self, memo: Dict = None) -> 'SubPattern':
|
||||
memo = {} if memo is None else memo
|
||||
new = copy.copy(self)
|
||||
LockableImpl.unlock(new)
|
||||
new.pattern = copy.deepcopy(self.pattern, memo)
|
||||
new.repetition = copy.deepcopy(self.repetition, memo)
|
||||
new.annotations = copy.deepcopy(self.annotations, memo)
|
||||
new.set_locked(self.locked)
|
||||
)
|
||||
return new
|
||||
|
||||
# pattern property
|
||||
@ -139,7 +127,7 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
||||
`SubPattern`'s properties.
|
||||
"""
|
||||
assert(self.pattern is not None)
|
||||
pattern = self.pattern.deepcopy().deepunlock()
|
||||
pattern = self.pattern.deepcopy()
|
||||
if self.scale != 1:
|
||||
pattern.scale_by(self.scale)
|
||||
if numpy.any(self.mirrored):
|
||||
@ -187,62 +175,10 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
||||
return None
|
||||
return self.as_pattern().get_bounds()
|
||||
|
||||
def lock(self: S) -> S:
|
||||
"""
|
||||
Lock the SubPattern, disallowing changes
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
self.mirrored.flags.writeable = False
|
||||
PositionableImpl._lock(self)
|
||||
LockableImpl.lock(self)
|
||||
return self
|
||||
|
||||
def unlock(self: S) -> S:
|
||||
"""
|
||||
Unlock the SubPattern
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
LockableImpl.unlock(self)
|
||||
PositionableImpl._unlock(self)
|
||||
self.mirrored.flags.writeable = True
|
||||
return self
|
||||
|
||||
def deeplock(self: S) -> S:
|
||||
"""
|
||||
Recursively lock the SubPattern and its contained pattern
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
assert(self.pattern is not None)
|
||||
self.lock()
|
||||
self.pattern.deeplock()
|
||||
return self
|
||||
|
||||
def deepunlock(self: S) -> S:
|
||||
"""
|
||||
Recursively unlock the SubPattern and its contained pattern
|
||||
|
||||
This is dangerous unless you have just performed a deepcopy, since
|
||||
the subpattern and its components may be used in more than one once!
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
assert(self.pattern is not None)
|
||||
self.unlock()
|
||||
self.pattern.deepunlock()
|
||||
return self
|
||||
|
||||
def __repr__(self) -> str:
|
||||
name = self.pattern.name if self.pattern is not None else None
|
||||
rotation = f' r{self.rotation*180/pi:g}' if self.rotation != 0 else ''
|
||||
scale = f' d{self.scale:g}' if self.scale != 1 else ''
|
||||
mirrored = ' m{:d}{:d}'.format(*self.mirrored) if self.mirrored.any() else ''
|
||||
dose = f' d{self.dose:g}' if self.dose != 1 else ''
|
||||
locked = ' L' if self.locked else ''
|
||||
return f'<SubPattern "{name}" at {self.offset}{rotation}{scale}{mirrored}{dose}{locked}>'
|
||||
return f'<SubPattern "{name}" at {self.offset}{rotation}{scale}{mirrored}{dose}>'
|
||||
|
@ -9,5 +9,4 @@ from .repeatable import Repeatable, RepeatableImpl
|
||||
from .scalable import Scalable, ScalableImpl
|
||||
from .mirrorable import Mirrorable
|
||||
from .copyable import Copyable
|
||||
from .lockable import Lockable, LockableImpl
|
||||
from .annotatable import Annotatable, AnnotatableImpl
|
||||
|
@ -44,9 +44,6 @@ class AnnotatableImpl(Annotatable, metaclass=ABCMeta):
|
||||
@property
|
||||
def annotations(self) -> annotations_t:
|
||||
return self._annotations
|
||||
# # TODO: Find a way to make sure the subclass implements Lockable without dealing with diamond inheritance or this extra hasattr
|
||||
# if hasattr(self, 'is_locked') and self.is_locked():
|
||||
# return MappingProxyType(self._annotations)
|
||||
|
||||
@annotations.setter
|
||||
def annotations(self, annotations: annotations_t):
|
||||
|
@ -1,103 +0,0 @@
|
||||
from typing import TypeVar, Dict, Tuple, Any
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
from ..error import PatternLockedError
|
||||
|
||||
|
||||
T = TypeVar('T', bound='Lockable')
|
||||
I = TypeVar('I', bound='LockableImpl')
|
||||
|
||||
|
||||
class Lockable(metaclass=ABCMeta):
|
||||
"""
|
||||
Abstract class for all lockable entities
|
||||
"""
|
||||
__slots__ = () # type: Tuple[str, ...]
|
||||
|
||||
'''
|
||||
---- Methods
|
||||
'''
|
||||
@abstractmethod
|
||||
def lock(self: T) -> T:
|
||||
"""
|
||||
Lock the object, disallowing further changes
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def unlock(self: T) -> T:
|
||||
"""
|
||||
Unlock the object, reallowing changes
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def is_locked(self) -> bool:
|
||||
"""
|
||||
Returns:
|
||||
True if the object is locked
|
||||
"""
|
||||
pass
|
||||
|
||||
def set_locked(self: T, locked: bool) -> T:
|
||||
"""
|
||||
Locks or unlocks based on the argument.
|
||||
No action if already in the requested state.
|
||||
|
||||
Args:
|
||||
locked: State to set.
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
if locked != self.is_locked():
|
||||
if locked:
|
||||
self.lock()
|
||||
else:
|
||||
self.unlock()
|
||||
return self
|
||||
|
||||
|
||||
class LockableImpl(Lockable, metaclass=ABCMeta):
|
||||
"""
|
||||
Simple implementation of Lockable
|
||||
"""
|
||||
__slots__ = () # type: Tuple[str, ...]
|
||||
|
||||
locked: bool
|
||||
""" If `True`, disallows changes to the object """
|
||||
|
||||
'''
|
||||
---- Non-abstract methods
|
||||
'''
|
||||
def __setattr__(self, name, value):
|
||||
if self.locked and name != 'locked':
|
||||
raise PatternLockedError()
|
||||
object.__setattr__(self, name, value)
|
||||
|
||||
def __getstate__(self) -> Dict[str, Any]:
|
||||
if hasattr(self, '__slots__'):
|
||||
return {key: getattr(self, key) for key in self.__slots__}
|
||||
else:
|
||||
return self.__dict__
|
||||
|
||||
def __setstate__(self, state: Dict[str, Any]) -> None:
|
||||
for k, v in state.items():
|
||||
object.__setattr__(self, k, v)
|
||||
|
||||
def lock(self: I) -> I:
|
||||
object.__setattr__(self, 'locked', True)
|
||||
return self
|
||||
|
||||
def unlock(self: I) -> I:
|
||||
object.__setattr__(self, 'locked', False)
|
||||
return self
|
||||
|
||||
def is_locked(self) -> bool:
|
||||
return self.locked
|
@ -120,23 +120,3 @@ class PositionableImpl(Positionable, metaclass=ABCMeta):
|
||||
def translate(self: I, offset: ArrayLike) -> I:
|
||||
self._offset += offset # type: ignore # NDArray += ArrayLike should be fine??
|
||||
return self
|
||||
|
||||
def _lock(self: I) -> I:
|
||||
"""
|
||||
Lock the entity, disallowing further changes
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
self._offset.flags.writeable = False
|
||||
return self
|
||||
|
||||
def _unlock(self: I) -> I:
|
||||
"""
|
||||
Unlock the entity
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
self._offset.flags.writeable = True
|
||||
return self
|
||||
|
Loading…
Reference in New Issue
Block a user