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.
|
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
|
- File I/O submodules are imported by `masque.file` to avoid creating hard dependencies on
|
||||||
external file-format reader/writers
|
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 .shapes import Shape
|
||||||
from .label import Label
|
from .label import Label
|
||||||
from .subpattern import SubPattern
|
from .subpattern import SubPattern
|
||||||
|
@ -11,13 +11,6 @@ class PatternError(MasqueError):
|
|||||||
"""
|
"""
|
||||||
pass
|
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):
|
class LibraryError(MasqueError):
|
||||||
"""
|
"""
|
||||||
|
@ -63,7 +63,7 @@ def write(
|
|||||||
patterns: A Pattern or list of patterns to write to the stream.
|
patterns: A Pattern or list of patterns to write to the stream.
|
||||||
stream: Stream object to write to.
|
stream: Stream object to write to.
|
||||||
modify_original: If `True`, the original pattern is modified as part of the writing
|
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`.
|
Default `False`.
|
||||||
disambiguate_func: Function which takes a list of patterns and alters them
|
disambiguate_func: Function which takes a list of patterns and alters them
|
||||||
to make their names valid and unique. Default is `disambiguate_pattern_names`.
|
to make their names valid and unique. Default is `disambiguate_pattern_names`.
|
||||||
@ -75,7 +75,7 @@ def write(
|
|||||||
assert(disambiguate_func is not None)
|
assert(disambiguate_func is not None)
|
||||||
|
|
||||||
if not modify_originals:
|
if not modify_originals:
|
||||||
pattern = pattern.deepcopy().deepunlock()
|
pattern = pattern.deepcopy()
|
||||||
|
|
||||||
# Get a dict of id(pattern) -> pattern
|
# Get a dict of id(pattern) -> pattern
|
||||||
patterns_by_id = pattern.referenced_patterns_by_id()
|
patterns_by_id = pattern.referenced_patterns_by_id()
|
||||||
|
@ -94,7 +94,7 @@ def write(
|
|||||||
library_name: Library name written into the GDSII file.
|
library_name: Library name written into the GDSII file.
|
||||||
Default 'masque-klamath'.
|
Default 'masque-klamath'.
|
||||||
modify_originals: If `True`, the original pattern is modified as part of the writing
|
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`.
|
Default `False`.
|
||||||
disambiguate_func: Function which takes a list of patterns and alters them
|
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
|
||||||
@ -109,7 +109,7 @@ def write(
|
|||||||
assert(disambiguate_func is not None) # placate mypy
|
assert(disambiguate_func is not None) # placate mypy
|
||||||
|
|
||||||
if not modify_originals:
|
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]
|
patterns = [p.wrap_repeated_shapes() for p in patterns]
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ def build(
|
|||||||
`fatamorgana.records.LayerName` entries.
|
`fatamorgana.records.LayerName` entries.
|
||||||
Default is an empty dict (no names provided).
|
Default is an empty dict (no names provided).
|
||||||
modify_originals: If `True`, the original pattern is modified as part of the writing
|
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`.
|
Default `False`.
|
||||||
disambiguate_func: Function which takes a list of patterns and alters them
|
disambiguate_func: Function which takes a list of patterns and alters them
|
||||||
to make their names valid and unique. Default is `disambiguate_pattern_names`.
|
to make their names valid and unique. Default is `disambiguate_pattern_names`.
|
||||||
@ -109,7 +109,7 @@ def build(
|
|||||||
annotations = {}
|
annotations = {}
|
||||||
|
|
||||||
if not modify_originals:
|
if not modify_originals:
|
||||||
patterns = [p.deepunlock() for p in copy.deepcopy(patterns)]
|
patterns = copy.deepcopy(patterns)
|
||||||
|
|
||||||
# Create library
|
# Create library
|
||||||
lib = fatamorgana.OasisLayout(unit=units_per_micron, validation=None)
|
lib = fatamorgana.OasisLayout(unit=units_per_micron, validation=None)
|
||||||
|
@ -95,7 +95,7 @@ def build(
|
|||||||
library_name: Library name written into the GDSII file.
|
library_name: Library name written into the GDSII file.
|
||||||
Default 'masque-gdsii-write'.
|
Default 'masque-gdsii-write'.
|
||||||
modify_originals: If `True`, the original pattern is modified as part of the writing
|
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`.
|
Default `False`.
|
||||||
disambiguate_func: Function which takes a list of patterns and alters them
|
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
|
||||||
@ -113,7 +113,7 @@ def build(
|
|||||||
assert(disambiguate_func is not None) # placate mypy
|
assert(disambiguate_func is not None) # placate mypy
|
||||||
|
|
||||||
if not modify_originals:
|
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]
|
patterns = [p.wrap_repeated_shapes() for p in patterns]
|
||||||
|
|
||||||
|
@ -6,14 +6,13 @@ from numpy.typing import ArrayLike, NDArray
|
|||||||
|
|
||||||
from .repetition import Repetition
|
from .repetition import Repetition
|
||||||
from .utils import rotation_matrix_2d, layer_t, AutoSlots, annotations_t
|
from .utils import rotation_matrix_2d, layer_t, AutoSlots, annotations_t
|
||||||
from .traits import PositionableImpl, LayerableImpl, Copyable, Pivotable, LockableImpl, RepeatableImpl
|
from .traits import PositionableImpl, LayerableImpl, Copyable, Pivotable, RepeatableImpl, AnnotatableImpl
|
||||||
from .traits import AnnotatableImpl
|
|
||||||
|
|
||||||
|
|
||||||
L = TypeVar('L', bound='Label')
|
L = TypeVar('L', bound='Label')
|
||||||
|
|
||||||
|
|
||||||
class Label(PositionableImpl, LayerableImpl, LockableImpl, RepeatableImpl, AnnotatableImpl,
|
class Label(PositionableImpl, LayerableImpl, RepeatableImpl, AnnotatableImpl,
|
||||||
Pivotable, Copyable, metaclass=AutoSlots):
|
Pivotable, Copyable, metaclass=AutoSlots):
|
||||||
"""
|
"""
|
||||||
A text annotation with a position and layer (but no size; it is not drawn)
|
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,
|
layer: layer_t = 0,
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
annotations: Optional[annotations_t] = None,
|
annotations: Optional[annotations_t] = None,
|
||||||
locked: bool = False,
|
|
||||||
identifier: Tuple = (),
|
identifier: Tuple = (),
|
||||||
) -> None:
|
) -> None:
|
||||||
LockableImpl.unlock(self)
|
|
||||||
self.identifier = identifier
|
self.identifier = identifier
|
||||||
self.string = string
|
self.string = string
|
||||||
self.offset = numpy.array(offset, dtype=float, copy=True)
|
self.offset = numpy.array(offset, dtype=float, copy=True)
|
||||||
self.layer = layer
|
self.layer = layer
|
||||||
self.repetition = repetition
|
self.repetition = repetition
|
||||||
self.annotations = annotations if annotations is not None else {}
|
self.annotations = annotations if annotations is not None else {}
|
||||||
self.set_locked(locked)
|
|
||||||
|
|
||||||
def __copy__(self: L) -> L:
|
def __copy__(self: L) -> L:
|
||||||
return type(self)(string=self.string,
|
return type(self)(
|
||||||
offset=self.offset.copy(),
|
string=self.string,
|
||||||
layer=self.layer,
|
offset=self.offset.copy(),
|
||||||
repetition=self.repetition,
|
layer=self.layer,
|
||||||
locked=self.locked,
|
repetition=self.repetition,
|
||||||
identifier=self.identifier)
|
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
|
|
||||||
|
|
||||||
def rotate_around(self: L, pivot: ArrayLike, rotation: float) -> L:
|
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])
|
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:
|
def __repr__(self) -> str:
|
||||||
locked = ' L' if self.locked else ''
|
return f'<Label "{self.string}" l{self.layer} o{self.offset}>'
|
||||||
return f'<Label "{self.string}" l{self.layer} o{self.offset}{locked}>'
|
|
||||||
|
@ -18,9 +18,8 @@ from .subpattern import SubPattern
|
|||||||
from .shapes import Shape, Polygon
|
from .shapes import Shape, Polygon
|
||||||
from .label import Label
|
from .label import Label
|
||||||
from .utils import rotation_matrix_2d, normalize_mirror, AutoSlots, annotations_t
|
from .utils import rotation_matrix_2d, normalize_mirror, AutoSlots, annotations_t
|
||||||
from .error import PatternError, PatternLockedError
|
from .error import PatternError
|
||||||
from .traits import LockableImpl, AnnotatableImpl, Scalable, Mirrorable
|
from .traits import AnnotatableImpl, Scalable, Mirrorable, Rotatable, Positionable
|
||||||
from .traits import Rotatable, Positionable
|
|
||||||
|
|
||||||
|
|
||||||
visitor_function_t = Callable[['Pattern', Tuple['Pattern'], Dict, NDArray[numpy.float64]], 'Pattern']
|
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')
|
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
|
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.
|
(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] = (),
|
labels: Sequence[Label] = (),
|
||||||
subpatterns: Sequence[SubPattern] = (),
|
subpatterns: Sequence[SubPattern] = (),
|
||||||
annotations: Optional[annotations_t] = None,
|
annotations: Optional[annotations_t] = None,
|
||||||
locked: bool = False,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Basic init; arguments get assigned to member variables.
|
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
|
labels: Initial labels in the Pattern
|
||||||
subpatterns: Initial subpatterns in the Pattern
|
subpatterns: Initial subpatterns in the Pattern
|
||||||
name: An identifier for the Pattern
|
name: An identifier for the Pattern
|
||||||
locked: Whether to lock the pattern after construction
|
|
||||||
"""
|
"""
|
||||||
LockableImpl.unlock(self)
|
|
||||||
if isinstance(shapes, list):
|
if isinstance(shapes, list):
|
||||||
self.shapes = shapes
|
self.shapes = shapes
|
||||||
else:
|
else:
|
||||||
@ -92,15 +88,15 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
|||||||
|
|
||||||
self.annotations = annotations if annotations is not None else {}
|
self.annotations = annotations if annotations is not None else {}
|
||||||
self.name = name
|
self.name = name
|
||||||
self.set_locked(locked)
|
|
||||||
|
|
||||||
def __copy__(self, memo: Dict = None) -> 'Pattern':
|
def __copy__(self, memo: Dict = None) -> 'Pattern':
|
||||||
return Pattern(name=self.name,
|
return Pattern(
|
||||||
shapes=copy.deepcopy(self.shapes),
|
name=self.name,
|
||||||
labels=copy.deepcopy(self.labels),
|
shapes=copy.deepcopy(self.shapes),
|
||||||
subpatterns=[copy.copy(sp) for sp in self.subpatterns],
|
labels=copy.deepcopy(self.labels),
|
||||||
annotations=copy.deepcopy(self.annotations),
|
subpatterns=[copy.copy(sp) for sp in self.subpatterns],
|
||||||
locked=self.locked)
|
annotations=copy.deepcopy(self.annotations),
|
||||||
|
)
|
||||||
|
|
||||||
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
|
||||||
@ -110,7 +106,7 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
|||||||
labels=copy.deepcopy(self.labels, memo),
|
labels=copy.deepcopy(self.labels, memo),
|
||||||
subpatterns=copy.deepcopy(self.subpatterns, memo),
|
subpatterns=copy.deepcopy(self.subpatterns, memo),
|
||||||
annotations=copy.deepcopy(self.annotations, memo),
|
annotations=copy.deepcopy(self.annotations, memo),
|
||||||
locked=self.locked)
|
)
|
||||||
return new
|
return new
|
||||||
|
|
||||||
def rename(self: P, name: str) -> P:
|
def rename(self: P, name: str) -> P:
|
||||||
@ -307,14 +303,13 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
|||||||
sp_transform = False
|
sp_transform = False
|
||||||
|
|
||||||
if subpattern.pattern is not None:
|
if subpattern.pattern is not None:
|
||||||
result = subpattern.pattern.dfs(visit_before=visit_before,
|
subpattern.patern = subpattern.pattern.dfs(
|
||||||
visit_after=visit_after,
|
visit_before=visit_before,
|
||||||
transform=sp_transform,
|
visit_after=visit_after,
|
||||||
memo=memo,
|
transform=sp_transform,
|
||||||
hierarchy=hierarchy + (self,))
|
memo=memo,
|
||||||
if result is not subpattern.pattern:
|
hierarchy=hierarchy + (self,),
|
||||||
# skip assignment to avoid PatternLockedError unless modified
|
)
|
||||||
subpattern.pattern = result
|
|
||||||
|
|
||||||
if visit_after is not None:
|
if visit_after is not None:
|
||||||
pat = visit_after(pat, hierarchy=hierarchy, memo=memo, transform=transform) # type: ignore
|
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
|
A list of `(Ni, 2)` `numpy.ndarray`s specifying vertices of the polygons. Each ndarray
|
||||||
is of the form `[[x0, y0], [x1, y1],...]`.
|
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
|
return [shape.vertices + shape.offset for shape in pat.shapes] # type: ignore # mypy can't figure out that shapes are all Polygons now
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@ -872,66 +867,6 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
|||||||
self.subpatterns.append(SubPattern(*args, **kwargs))
|
self.subpatterns.append(SubPattern(*args, **kwargs))
|
||||||
return self
|
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
|
@staticmethod
|
||||||
def load(filename: str) -> 'Pattern':
|
def load(filename: str) -> 'Pattern':
|
||||||
"""
|
"""
|
||||||
@ -1046,5 +981,4 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
|||||||
return toplevel
|
return toplevel
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
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)}>')
|
||||||
return (f'<Pattern "{self.name}": sh{len(self.shapes)} sp{len(self.subpatterns)} la{len(self.labels)}{locked}>')
|
|
||||||
|
@ -12,7 +12,7 @@ from numpy.typing import ArrayLike, NDArray
|
|||||||
|
|
||||||
from .error import PatternError
|
from .error import PatternError
|
||||||
from .utils import rotation_matrix_2d, AutoSlots
|
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):
|
class Repetition(Copyable, Rotatable, Mirrorable, Scalable, metaclass=ABCMeta):
|
||||||
@ -30,7 +30,7 @@ class Repetition(Copyable, Rotatable, Mirrorable, Scalable, metaclass=ABCMeta):
|
|||||||
pass
|
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).
|
`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,
|
a_count: int,
|
||||||
b_vector: Optional[ArrayLike] = None,
|
b_vector: Optional[ArrayLike] = None,
|
||||||
b_count: Optional[int] = 1,
|
b_count: Optional[int] = 1,
|
||||||
locked: bool = False,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
@ -79,7 +78,6 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
|||||||
Can be omitted when specifying a 1D array.
|
Can be omitted when specifying a 1D array.
|
||||||
b_count: Number of elements in the `b_vector` direction.
|
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.
|
||||||
locked: Whether the `Grid` is locked after initialization.
|
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
PatternError if `b_*` inputs conflict with each other
|
PatternError if `b_*` inputs conflict with each other
|
||||||
@ -99,12 +97,10 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
|||||||
if b_count < 1:
|
if b_count < 1:
|
||||||
raise PatternError(f'Repetition has too-small b_count: {b_count}')
|
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.a_vector = a_vector # type: ignore # setter handles type conversion
|
||||||
self.b_vector = b_vector # type: ignore # setter handles type conversion
|
self.b_vector = b_vector # type: ignore # setter handles type conversion
|
||||||
self.a_count = a_count
|
self.a_count = a_count
|
||||||
self.b_count = b_count
|
self.b_count = b_count
|
||||||
self.locked = locked
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def aligned(
|
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)
|
return cls(a_vector=(x, 0), b_vector=(0, y), a_count=x_count, b_count=y_count)
|
||||||
|
|
||||||
def __copy__(self) -> 'Grid':
|
def __copy__(self) -> 'Grid':
|
||||||
new = Grid(a_vector=self.a_vector.copy(),
|
new = Grid(
|
||||||
b_vector=copy.copy(self.b_vector),
|
a_vector=self.a_vector.copy(),
|
||||||
a_count=self.a_count,
|
b_vector=copy.copy(self.b_vector),
|
||||||
b_count=self.b_count,
|
a_count=self.a_count,
|
||||||
locked=self.locked)
|
b_count=self.b_count,
|
||||||
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
|
return new
|
||||||
|
|
||||||
# a_vector property
|
# a_vector property
|
||||||
@ -264,36 +254,9 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
|||||||
self.b_vector *= c
|
self.b_vector *= c
|
||||||
return self
|
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:
|
def __repr__(self) -> str:
|
||||||
locked = ' L' if self.locked else ''
|
|
||||||
bv = f', {self.b_vector}' if self.b_vector is not None 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:
|
def __eq__(self, other: Any) -> bool:
|
||||||
if not isinstance(other, type(self)):
|
if not isinstance(other, type(self)):
|
||||||
@ -308,12 +271,10 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
|||||||
return False
|
return False
|
||||||
if any(self.b_vector[ii] != other.b_vector[ii] for ii in range(2)):
|
if any(self.b_vector[ii] != other.b_vector[ii] for ii in range(2)):
|
||||||
return False
|
return False
|
||||||
if self.locked != other.locked:
|
|
||||||
return False
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class Arbitrary(LockableImpl, Repetition, metaclass=AutoSlots):
|
class Arbitrary(Repetition, metaclass=AutoSlots):
|
||||||
"""
|
"""
|
||||||
`Arbitrary` is a simple list of (absolute) displacements for instances.
|
`Arbitrary` is a simple list of (absolute) displacements for instances.
|
||||||
|
|
||||||
@ -342,48 +303,19 @@ class Arbitrary(LockableImpl, Repetition, metaclass=AutoSlots):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
displacements: ArrayLike,
|
displacements: ArrayLike,
|
||||||
locked: bool = False,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
displacements: List of vectors (Nx2 ndarray) specifying displacements.
|
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.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:
|
def __repr__(self) -> str:
|
||||||
locked = ' L' if self.locked else ''
|
return (f'<Arbitrary {len(self.displacements)}pts>')
|
||||||
return (f'<Arbitrary {len(self.displacements)}pts {locked}>')
|
|
||||||
|
|
||||||
def __eq__(self, other: Any) -> bool:
|
def __eq__(self, other: Any) -> bool:
|
||||||
if not isinstance(other, type(self)):
|
if not isinstance(other, type(self)):
|
||||||
return False
|
return False
|
||||||
if self.locked != other.locked:
|
|
||||||
return False
|
|
||||||
return numpy.array_equal(self.displacements, other.displacements)
|
return numpy.array_equal(self.displacements, other.displacements)
|
||||||
|
|
||||||
def rotate(self, rotation: float) -> 'Arbitrary':
|
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 .. import PatternError
|
||||||
from ..repetition import Repetition
|
from ..repetition import Repetition
|
||||||
from ..utils import is_scalar, layer_t, AutoSlots, annotations_t
|
from ..utils import is_scalar, layer_t, AutoSlots, annotations_t
|
||||||
from ..traits import LockableImpl
|
|
||||||
|
|
||||||
|
|
||||||
class Arc(Shape, metaclass=AutoSlots):
|
class Arc(Shape, metaclass=AutoSlots):
|
||||||
@ -166,10 +165,8 @@ class Arc(Shape, metaclass=AutoSlots):
|
|||||||
dose: float = 1.0,
|
dose: float = 1.0,
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
annotations: Optional[annotations_t] = None,
|
annotations: Optional[annotations_t] = None,
|
||||||
locked: bool = False,
|
|
||||||
raw: bool = False,
|
raw: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
LockableImpl.unlock(self)
|
|
||||||
self.identifier = ()
|
self.identifier = ()
|
||||||
if raw:
|
if raw:
|
||||||
assert(isinstance(radii, numpy.ndarray))
|
assert(isinstance(radii, numpy.ndarray))
|
||||||
@ -197,18 +194,6 @@ class Arc(Shape, metaclass=AutoSlots):
|
|||||||
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.mirror(a) for a, do in enumerate(mirrored) if do]
|
[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(
|
def to_polygons(
|
||||||
self,
|
self,
|
||||||
@ -429,21 +414,8 @@ class Arc(Shape, metaclass=AutoSlots):
|
|||||||
a.append((a0, a1))
|
a.append((a0, a1))
|
||||||
return numpy.array(a)
|
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:
|
def __repr__(self) -> str:
|
||||||
angles = f' a°{numpy.rad2deg(self.angles)}'
|
angles = f' a°{numpy.rad2deg(self.angles)}'
|
||||||
rotation = f' r°{numpy.rad2deg(self.rotation):g}' if self.rotation != 0 else ''
|
rotation = f' r°{numpy.rad2deg(self.rotation):g}' if self.rotation != 0 else ''
|
||||||
dose = f' d{self.dose:g}' if self.dose != 1 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}>'
|
||||||
return f'<Arc l{self.layer} o{self.offset} r{self.radii}{angles} w{self.width:g}{rotation}{dose}{locked}>'
|
|
||||||
|
@ -9,7 +9,6 @@ from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS
|
|||||||
from .. import PatternError
|
from .. import PatternError
|
||||||
from ..repetition import Repetition
|
from ..repetition import Repetition
|
||||||
from ..utils import is_scalar, layer_t, AutoSlots, annotations_t
|
from ..utils import is_scalar, layer_t, AutoSlots, annotations_t
|
||||||
from ..traits import LockableImpl
|
|
||||||
|
|
||||||
|
|
||||||
class Circle(Shape, metaclass=AutoSlots):
|
class Circle(Shape, metaclass=AutoSlots):
|
||||||
@ -54,10 +53,8 @@ class Circle(Shape, metaclass=AutoSlots):
|
|||||||
dose: float = 1.0,
|
dose: float = 1.0,
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
annotations: Optional[annotations_t] = None,
|
annotations: Optional[annotations_t] = None,
|
||||||
locked: bool = False,
|
|
||||||
raw: bool = False,
|
raw: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
LockableImpl.unlock(self)
|
|
||||||
self.identifier = ()
|
self.identifier = ()
|
||||||
if raw:
|
if raw:
|
||||||
assert(isinstance(offset, numpy.ndarray))
|
assert(isinstance(offset, numpy.ndarray))
|
||||||
@ -76,16 +73,6 @@ class Circle(Shape, metaclass=AutoSlots):
|
|||||||
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.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(
|
def to_polygons(
|
||||||
self,
|
self,
|
||||||
@ -138,5 +125,4 @@ class Circle(Shape, metaclass=AutoSlots):
|
|||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
dose = f' d{self.dose:g}' if self.dose != 1 else ''
|
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}>'
|
||||||
return f'<Circle l{self.layer} o{self.offset} r{self.radius:g}{dose}{locked}>'
|
|
||||||
|
@ -10,7 +10,6 @@ from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS
|
|||||||
from .. import PatternError
|
from .. import PatternError
|
||||||
from ..repetition import Repetition
|
from ..repetition import Repetition
|
||||||
from ..utils import is_scalar, rotation_matrix_2d, layer_t, AutoSlots, annotations_t
|
from ..utils import is_scalar, rotation_matrix_2d, layer_t, AutoSlots, annotations_t
|
||||||
from ..traits import LockableImpl
|
|
||||||
|
|
||||||
|
|
||||||
class Ellipse(Shape, metaclass=AutoSlots):
|
class Ellipse(Shape, metaclass=AutoSlots):
|
||||||
@ -101,10 +100,8 @@ class Ellipse(Shape, metaclass=AutoSlots):
|
|||||||
dose: float = 1.0,
|
dose: float = 1.0,
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
annotations: Optional[annotations_t] = None,
|
annotations: Optional[annotations_t] = None,
|
||||||
locked: bool = False,
|
|
||||||
raw: bool = False,
|
raw: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
LockableImpl.unlock(self)
|
|
||||||
self.identifier = ()
|
self.identifier = ()
|
||||||
if raw:
|
if raw:
|
||||||
assert(isinstance(radii, numpy.ndarray))
|
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.mirror(a) for a, do in enumerate(mirrored) if do]
|
||||||
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.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(
|
def to_polygons(
|
||||||
self,
|
self,
|
||||||
@ -209,18 +195,7 @@ class Ellipse(Shape, metaclass=AutoSlots):
|
|||||||
(self.offset, scale / norm_value, angle, False, self.dose),
|
(self.offset, scale / norm_value, angle, False, self.dose),
|
||||||
lambda: Ellipse(radii=radii * norm_value, layer=self.layer))
|
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:
|
def __repr__(self) -> str:
|
||||||
rotation = f' r{self.rotation*180/pi:g}' if self.rotation != 0 else ''
|
rotation = f' r{self.rotation*180/pi:g}' if self.rotation != 0 else ''
|
||||||
dose = f' d{self.dose:g}' if self.dose != 1 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}>'
|
||||||
return f'<Ellipse l{self.layer} o{self.offset} r{self.radii}{rotation}{dose}{locked}>'
|
|
||||||
|
@ -11,7 +11,6 @@ from .. import PatternError
|
|||||||
from ..repetition import Repetition
|
from ..repetition import Repetition
|
||||||
from ..utils import is_scalar, rotation_matrix_2d, layer_t, AutoSlots
|
from ..utils import is_scalar, rotation_matrix_2d, layer_t, AutoSlots
|
||||||
from ..utils import remove_colinear_vertices, remove_duplicate_vertices, annotations_t
|
from ..utils import remove_colinear_vertices, remove_duplicate_vertices, annotations_t
|
||||||
from ..traits import LockableImpl
|
|
||||||
|
|
||||||
|
|
||||||
class PathCap(Enum):
|
class PathCap(Enum):
|
||||||
@ -155,10 +154,8 @@ class Path(Shape, metaclass=AutoSlots):
|
|||||||
dose: float = 1.0,
|
dose: float = 1.0,
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
annotations: Optional[annotations_t] = None,
|
annotations: Optional[annotations_t] = None,
|
||||||
locked: bool = False,
|
|
||||||
raw: bool = False,
|
raw: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
LockableImpl.unlock(self)
|
|
||||||
self._cap_extensions = None # Since .cap setter might access it
|
self._cap_extensions = None # Since .cap setter might access it
|
||||||
|
|
||||||
self.identifier = ()
|
self.identifier = ()
|
||||||
@ -187,18 +184,15 @@ class Path(Shape, metaclass=AutoSlots):
|
|||||||
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.set_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
|
||||||
new = copy.copy(self)
|
new = copy.copy(self)
|
||||||
Shape.unlock(new)
|
|
||||||
new._offset = self._offset.copy()
|
new._offset = self._offset.copy()
|
||||||
new._vertices = self._vertices.copy()
|
new._vertices = self._vertices.copy()
|
||||||
new._cap = copy.deepcopy(self._cap, memo)
|
new._cap = copy.deepcopy(self._cap, memo)
|
||||||
new._cap_extensions = copy.deepcopy(self._cap_extensions, memo)
|
new._cap_extensions = copy.deepcopy(self._cap_extensions, memo)
|
||||||
new._annotations = copy.deepcopy(self._annotations)
|
new._annotations = copy.deepcopy(self._annotations)
|
||||||
new.set_locked(self.locked)
|
|
||||||
return new
|
return new
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -424,22 +418,7 @@ class Path(Shape, metaclass=AutoSlots):
|
|||||||
extensions = numpy.zeros(2)
|
extensions = numpy.zeros(2)
|
||||||
return extensions
|
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:
|
def __repr__(self) -> str:
|
||||||
centroid = self.offset + self.vertices.mean(axis=0)
|
centroid = self.offset + self.vertices.mean(axis=0)
|
||||||
dose = f' d{self.dose:g}' if self.dose != 1 else ''
|
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}>'
|
||||||
return f'<Path l{self.layer} centroid {centroid} v{len(self.vertices)} w{self.width} c{self.cap}{dose}{locked}>'
|
|
||||||
|
@ -10,7 +10,6 @@ from .. import PatternError
|
|||||||
from ..repetition import Repetition
|
from ..repetition import Repetition
|
||||||
from ..utils import is_scalar, rotation_matrix_2d, layer_t, AutoSlots
|
from ..utils import is_scalar, rotation_matrix_2d, layer_t, AutoSlots
|
||||||
from ..utils import remove_colinear_vertices, remove_duplicate_vertices, annotations_t
|
from ..utils import remove_colinear_vertices, remove_duplicate_vertices, annotations_t
|
||||||
from ..traits import LockableImpl
|
|
||||||
|
|
||||||
|
|
||||||
class Polygon(Shape, metaclass=AutoSlots):
|
class Polygon(Shape, metaclass=AutoSlots):
|
||||||
@ -83,10 +82,8 @@ class Polygon(Shape, metaclass=AutoSlots):
|
|||||||
dose: float = 1.0,
|
dose: float = 1.0,
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
annotations: Optional[annotations_t] = None,
|
annotations: Optional[annotations_t] = None,
|
||||||
locked: bool = False,
|
|
||||||
raw: bool = False,
|
raw: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
LockableImpl.unlock(self)
|
|
||||||
self.identifier = ()
|
self.identifier = ()
|
||||||
if raw:
|
if raw:
|
||||||
assert(isinstance(vertices, numpy.ndarray))
|
assert(isinstance(vertices, numpy.ndarray))
|
||||||
@ -106,17 +103,6 @@ class Polygon(Shape, metaclass=AutoSlots):
|
|||||||
self.dose = dose
|
self.dose = dose
|
||||||
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.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
|
@staticmethod
|
||||||
def square(
|
def square(
|
||||||
@ -430,18 +416,7 @@ class Polygon(Shape, metaclass=AutoSlots):
|
|||||||
self.vertices = remove_colinear_vertices(self.vertices, closed_path=True)
|
self.vertices = remove_colinear_vertices(self.vertices, closed_path=True)
|
||||||
return self
|
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:
|
def __repr__(self) -> str:
|
||||||
centroid = self.offset + self.vertices.mean(axis=0)
|
centroid = self.offset + self.vertices.mean(axis=0)
|
||||||
dose = f' d{self.dose:g}' if self.dose != 1 else ''
|
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}>'
|
||||||
return f'<Polygon l{self.layer} centroid {centroid} v{len(self.vertices)}{dose}{locked}>'
|
|
||||||
|
@ -4,10 +4,11 @@ from abc import ABCMeta, abstractmethod
|
|||||||
import numpy
|
import numpy
|
||||||
from numpy.typing import NDArray, ArrayLike
|
from numpy.typing import NDArray, ArrayLike
|
||||||
|
|
||||||
from ..traits import (PositionableImpl, LayerableImpl, DoseableImpl,
|
from ..traits import (
|
||||||
Rotatable, Mirrorable, Copyable, Scalable,
|
PositionableImpl, LayerableImpl, DoseableImpl,
|
||||||
PivotableImpl, LockableImpl, RepeatableImpl,
|
Rotatable, Mirrorable, Copyable, Scalable,
|
||||||
AnnotatableImpl)
|
PivotableImpl, RepeatableImpl, AnnotatableImpl,
|
||||||
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from . import Polygon
|
from . import Polygon
|
||||||
@ -27,7 +28,7 @@ T = TypeVar('T', bound='Shape')
|
|||||||
|
|
||||||
|
|
||||||
class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable, Copyable, Scalable,
|
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.
|
Abstract class specifying functions common to all shapes.
|
||||||
"""
|
"""
|
||||||
@ -36,13 +37,6 @@ class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable
|
|||||||
identifier: Tuple
|
identifier: Tuple
|
||||||
""" An arbitrary identifier for the shape, usually empty but used by `Pattern.flatten()` """
|
""" 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
|
--- Abstract methods
|
||||||
'''
|
'''
|
||||||
@ -303,13 +297,3 @@ class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable
|
|||||||
dose=self.dose))
|
dose=self.dose))
|
||||||
|
|
||||||
return manhattan_polygons
|
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 ..traits import RotatableImpl
|
||||||
from ..utils import is_scalar, get_bit, normalize_mirror, layer_t, AutoSlots
|
from ..utils import is_scalar, get_bit, normalize_mirror, layer_t, AutoSlots
|
||||||
from ..utils import annotations_t
|
from ..utils import annotations_t
|
||||||
from ..traits import LockableImpl
|
|
||||||
|
|
||||||
# Loaded on use:
|
# Loaded on use:
|
||||||
# from freetype import Face
|
# from freetype import Face
|
||||||
@ -74,10 +73,8 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
|
|||||||
dose: float = 1.0,
|
dose: float = 1.0,
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
annotations: Optional[annotations_t] = None,
|
annotations: Optional[annotations_t] = None,
|
||||||
locked: bool = False,
|
|
||||||
raw: bool = False,
|
raw: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
LockableImpl.unlock(self)
|
|
||||||
self.identifier = ()
|
self.identifier = ()
|
||||||
if raw:
|
if raw:
|
||||||
assert(isinstance(offset, numpy.ndarray))
|
assert(isinstance(offset, numpy.ndarray))
|
||||||
@ -102,17 +99,6 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
|
|||||||
self.repetition = repetition
|
self.repetition = repetition
|
||||||
self.annotations = annotations if annotations is not None else {}
|
self.annotations = annotations if annotations is not None else {}
|
||||||
self.font_path = font_path
|
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(
|
def to_polygons(
|
||||||
self,
|
self,
|
||||||
@ -259,19 +245,8 @@ def get_char_as_polygons(
|
|||||||
|
|
||||||
return polygons, advance
|
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:
|
def __repr__(self) -> str:
|
||||||
rotation = f' r°{self.rotation*180/pi:g}' if self.rotation != 0 else ''
|
rotation = f' r°{self.rotation*180/pi:g}' if self.rotation != 0 else ''
|
||||||
dose = f' d{self.dose:g}' if self.dose != 1 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 ''
|
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 .error import PatternError
|
||||||
from .utils import is_scalar, AutoSlots, annotations_t
|
from .utils import is_scalar, AutoSlots, annotations_t
|
||||||
from .repetition import Repetition
|
from .repetition import Repetition
|
||||||
from .traits import (PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl,
|
from .traits import (
|
||||||
Mirrorable, PivotableImpl, Copyable, LockableImpl, RepeatableImpl,
|
PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl,
|
||||||
AnnotatableImpl)
|
Mirrorable, PivotableImpl, Copyable, RepeatableImpl, AnnotatableImpl
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -27,7 +28,7 @@ S = TypeVar('S', bound='SubPattern')
|
|||||||
|
|
||||||
|
|
||||||
class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mirrorable,
|
class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mirrorable,
|
||||||
PivotableImpl, Copyable, RepeatableImpl, LockableImpl, AnnotatableImpl,
|
PivotableImpl, Copyable, RepeatableImpl, AnnotatableImpl,
|
||||||
metaclass=AutoSlots):
|
metaclass=AutoSlots):
|
||||||
"""
|
"""
|
||||||
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
|
||||||
@ -58,7 +59,6 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
|||||||
scale: float = 1.0,
|
scale: float = 1.0,
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
annotations: Optional[annotations_t] = None,
|
annotations: Optional[annotations_t] = None,
|
||||||
locked: bool = False,
|
|
||||||
identifier: Tuple[Any, ...] = (),
|
identifier: Tuple[Any, ...] = (),
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
@ -70,10 +70,8 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
|||||||
dose: Scaling factor applied to the dose.
|
dose: Scaling factor applied to the dose.
|
||||||
scale: Scaling factor applied to the pattern's geometry.
|
scale: Scaling factor applied to the pattern's geometry.
|
||||||
repetition: TODO
|
repetition: TODO
|
||||||
locked: Whether the `SubPattern` is locked after initialization.
|
|
||||||
identifier: Arbitrary tuple, used internally by some `masque` functions.
|
identifier: Arbitrary tuple, used internally by some `masque` functions.
|
||||||
"""
|
"""
|
||||||
LockableImpl.unlock(self)
|
|
||||||
self.identifier = identifier
|
self.identifier = identifier
|
||||||
self.pattern = pattern
|
self.pattern = pattern
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
@ -85,28 +83,18 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
|||||||
self.mirrored = mirrored
|
self.mirrored = mirrored
|
||||||
self.repetition = repetition
|
self.repetition = repetition
|
||||||
self.annotations = annotations if annotations is not None else {}
|
self.annotations = annotations if annotations is not None else {}
|
||||||
self.set_locked(locked)
|
|
||||||
|
|
||||||
def __copy__(self) -> 'SubPattern':
|
def __copy__(self) -> 'SubPattern':
|
||||||
new = SubPattern(pattern=self.pattern,
|
new = SubPattern(
|
||||||
offset=self.offset.copy(),
|
pattern=self.pattern,
|
||||||
rotation=self.rotation,
|
offset=self.offset.copy(),
|
||||||
dose=self.dose,
|
rotation=self.rotation,
|
||||||
scale=self.scale,
|
dose=self.dose,
|
||||||
mirrored=self.mirrored.copy(),
|
scale=self.scale,
|
||||||
repetition=copy.deepcopy(self.repetition),
|
mirrored=self.mirrored.copy(),
|
||||||
annotations=copy.deepcopy(self.annotations),
|
repetition=copy.deepcopy(self.repetition),
|
||||||
locked=self.locked)
|
annotations=copy.deepcopy(self.annotations),
|
||||||
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
|
return new
|
||||||
|
|
||||||
# pattern property
|
# pattern property
|
||||||
@ -139,7 +127,7 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
|||||||
`SubPattern`'s properties.
|
`SubPattern`'s properties.
|
||||||
"""
|
"""
|
||||||
assert(self.pattern is not None)
|
assert(self.pattern is not None)
|
||||||
pattern = self.pattern.deepcopy().deepunlock()
|
pattern = self.pattern.deepcopy()
|
||||||
if self.scale != 1:
|
if self.scale != 1:
|
||||||
pattern.scale_by(self.scale)
|
pattern.scale_by(self.scale)
|
||||||
if numpy.any(self.mirrored):
|
if numpy.any(self.mirrored):
|
||||||
@ -187,62 +175,10 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
|||||||
return None
|
return None
|
||||||
return self.as_pattern().get_bounds()
|
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:
|
def __repr__(self) -> str:
|
||||||
name = self.pattern.name if self.pattern is not None else None
|
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 ''
|
rotation = f' r{self.rotation*180/pi:g}' if self.rotation != 0 else ''
|
||||||
scale = f' d{self.scale:g}' if self.scale != 1 else ''
|
scale = f' d{self.scale:g}' if self.scale != 1 else ''
|
||||||
mirrored = ' m{:d}{:d}'.format(*self.mirrored) if self.mirrored.any() else ''
|
mirrored = ' m{:d}{:d}'.format(*self.mirrored) if self.mirrored.any() else ''
|
||||||
dose = f' d{self.dose:g}' if self.dose != 1 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}>'
|
||||||
return f'<SubPattern "{name}" at {self.offset}{rotation}{scale}{mirrored}{dose}{locked}>'
|
|
||||||
|
@ -9,5 +9,4 @@ from .repeatable import Repeatable, RepeatableImpl
|
|||||||
from .scalable import Scalable, ScalableImpl
|
from .scalable import Scalable, ScalableImpl
|
||||||
from .mirrorable import Mirrorable
|
from .mirrorable import Mirrorable
|
||||||
from .copyable import Copyable
|
from .copyable import Copyable
|
||||||
from .lockable import Lockable, LockableImpl
|
|
||||||
from .annotatable import Annotatable, AnnotatableImpl
|
from .annotatable import Annotatable, AnnotatableImpl
|
||||||
|
@ -44,9 +44,6 @@ class AnnotatableImpl(Annotatable, metaclass=ABCMeta):
|
|||||||
@property
|
@property
|
||||||
def annotations(self) -> annotations_t:
|
def annotations(self) -> annotations_t:
|
||||||
return self._annotations
|
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
|
@annotations.setter
|
||||||
def annotations(self, annotations: annotations_t):
|
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:
|
def translate(self: I, offset: ArrayLike) -> I:
|
||||||
self._offset += offset # type: ignore # NDArray += ArrayLike should be fine??
|
self._offset += offset # type: ignore # NDArray += ArrayLike should be fine??
|
||||||
return self
|
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