flake8-aided fixes
This commit is contained in:
parent
db9b39dbc0
commit
6b01b43559
@ -15,7 +15,7 @@ to output to multiple formats.
|
||||
Requirements:
|
||||
* python >= 3.8
|
||||
* numpy
|
||||
* klamath (used for `gdsii` i/o and library management)
|
||||
* klamath (optional, used for `gdsii` i/o)
|
||||
* matplotlib (optional, used for `visualization` functions and `text`)
|
||||
* ezdxf (optional, used for `dxf` i/o)
|
||||
* fatamorgana (optional, used for `oasis` i/o)
|
||||
@ -49,9 +49,3 @@ pip3 install git+https://mpxd.net/code/jan/masque.git@release
|
||||
- boolean ops
|
||||
* Construct polygons from bitmap using `skimage.find_contours`
|
||||
* Deal with shape repetitions for dxf, svg
|
||||
|
||||
* this approach has an issue: how add devices together?
|
||||
- need to add the other device by name
|
||||
- need to know the other device's ports
|
||||
-
|
||||
- also: device doesn't know its own name, can't wrap itself into a ref
|
||||
|
41
examples/pic2mask.py
Normal file
41
examples/pic2mask.py
Normal file
@ -0,0 +1,41 @@
|
||||
# pip install pillow scikit-image
|
||||
# or
|
||||
# sudo apt install python3-pil python3-skimage
|
||||
|
||||
from PIL import Image
|
||||
from skimage.measure import find_contours
|
||||
from matplotlib import pyplot
|
||||
import numpy
|
||||
|
||||
from masque import Pattern, Library, Polygon
|
||||
from masque.file.gdsii import writefile
|
||||
|
||||
#
|
||||
# Read the image into a numpy array
|
||||
#
|
||||
im = Image.open('./Desktop/Camera/IMG_20220626_091101.jpg')
|
||||
|
||||
aa = numpy.array(im.convert(mode='L').getdata()).reshape(im.height, im.width)
|
||||
|
||||
threshold = (aa.max() - aa.min()) / 2
|
||||
|
||||
#
|
||||
# Find edge contours and plot them
|
||||
#
|
||||
contours = find_contours(aa, threshold)
|
||||
|
||||
pyplot.imshow(aa)
|
||||
for contour in contours:
|
||||
pyplot.plot(contour[:, 1], contour[:, 0], linewidth=2)
|
||||
pyplot.show(block=False)
|
||||
|
||||
#
|
||||
# Create the layout from the contours
|
||||
#
|
||||
pat = Pattern()
|
||||
pat.shapes = [Polygon(vertices=vv) for vv in contours if len(vv) < 1_000]
|
||||
|
||||
lib = {}
|
||||
lib['my_mask_name'] = pat
|
||||
|
||||
writefile(lib, 'test_contours.gds', meters_per_unit=1e-9)
|
@ -28,7 +28,7 @@
|
||||
"""
|
||||
|
||||
from .error import MasqueError, PatternError, LibraryError, BuildError
|
||||
from .shapes import Shape
|
||||
from .shapes import Shape, Polygon, Path, Circle, Arc, Ellipse
|
||||
from .label import Label
|
||||
from .ref import Ref
|
||||
from .pattern import Pattern
|
||||
|
@ -1,17 +1,12 @@
|
||||
from typing import Dict, Iterable, List, Tuple, Union, TypeVar, Any, Iterator, Optional, Sequence
|
||||
from typing import overload, KeysView, ValuesView, MutableMapping, Mapping
|
||||
from typing import Dict, Tuple, Union, TypeVar, Optional, Sequence
|
||||
from typing import MutableMapping, Mapping
|
||||
import copy
|
||||
import warnings
|
||||
import traceback
|
||||
import logging
|
||||
from collections import Counter
|
||||
from abc import ABCMeta
|
||||
|
||||
import numpy
|
||||
from numpy import pi
|
||||
from numpy.typing import ArrayLike, NDArray
|
||||
from numpy.typing import ArrayLike
|
||||
|
||||
from ..traits import PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable
|
||||
from ..pattern import Pattern
|
||||
from ..ref import Ref
|
||||
from ..library import MutableLibrary
|
||||
@ -241,8 +236,6 @@ class Builder(PortList):
|
||||
`PortError` if applying the prefixes results in duplicate port
|
||||
names.
|
||||
"""
|
||||
from ..pattern import Pattern
|
||||
|
||||
if library is None:
|
||||
if hasattr(source, 'library') and isinstance(source, MutableLibrary):
|
||||
library = source.library
|
||||
@ -276,7 +269,7 @@ class Builder(PortList):
|
||||
ports_in = {f'{in_prefix}{name}': port.deepcopy().rotate(pi)
|
||||
for name, port in mapped_ports.items()}
|
||||
ports_out = {f'{out_prefix}{name}': port.deepcopy()
|
||||
for name, port in mapped_ports.items()}
|
||||
for name, port in mapped_ports.items()}
|
||||
|
||||
duplicates = set(ports_out.keys()) & set(ports_in.keys())
|
||||
if duplicates:
|
||||
@ -377,7 +370,7 @@ class Builder(PortList):
|
||||
map_out[vi] = None
|
||||
|
||||
self.place(other, offset=translation, rotation=rotation, pivot=pivot,
|
||||
mirrored=mirrored, port_map=map_out, skip_port_check=True)
|
||||
mirrored=mirrored, port_map=map_out, skip_port_check=True)
|
||||
return self
|
||||
|
||||
def place(
|
||||
|
@ -82,6 +82,7 @@ def pat2dev(
|
||||
|
||||
ports = {}
|
||||
annotated_cells = set()
|
||||
|
||||
def find_ports_each(pat, hierarchy, transform, memo) -> Pattern:
|
||||
if len(hierarchy) > max_depth:
|
||||
if max_depth >= 999_999:
|
||||
|
@ -1,5 +1,5 @@
|
||||
from typing import Dict, Tuple, List, Mapping, Sequence, SupportsFloat
|
||||
from typing import Optional, Union, Any, cast, TYPE_CHECKING
|
||||
from typing import Dict, Mapping, Sequence, SupportsFloat
|
||||
from typing import Optional, Union, cast, TYPE_CHECKING
|
||||
from pprint import pformat
|
||||
|
||||
import numpy
|
||||
@ -173,7 +173,7 @@ def ell(
|
||||
|
||||
if bound_type in ('emin', 'min_extension', 'min_past_furthest'):
|
||||
offsets += rot_bound.max()
|
||||
elif bound_type in('emax', 'max_extension'):
|
||||
elif bound_type in ('emax', 'max_extension'):
|
||||
offsets += rot_bound.min() - offsets.max()
|
||||
else:
|
||||
if numpy.size(bound) == 2:
|
||||
@ -194,7 +194,7 @@ def ell(
|
||||
if extension < 0:
|
||||
ext_floor = -numpy.floor(extension)
|
||||
raise BuildError(f'Position is too close by at least {ext_floor}. Total extensions would be\n\t'
|
||||
+ '\n\t'.join(f'{key}: {off}' for key, off in zip(ports.keys(), offsets)))
|
||||
+ '\n\t'.join(f'{key}: {off}' for key, off in zip(ports.keys(), offsets)))
|
||||
|
||||
result = dict(zip(ports.keys(), offsets))
|
||||
return result
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""
|
||||
DXF file format readers and writers
|
||||
"""
|
||||
from typing import List, Any, Dict, Tuple, Callable, Union, Sequence, Iterable, Mapping
|
||||
from typing import List, Any, Dict, Tuple, Callable, Union, Iterable, Mapping
|
||||
import re
|
||||
import io
|
||||
import base64
|
||||
@ -17,7 +17,6 @@ from .. import Pattern, Ref, PatternError, Label, Shape
|
||||
from ..shapes import Polygon, Path
|
||||
from ..repetition import Grid
|
||||
from ..utils import rotation_matrix_2d, layer_t
|
||||
from .gdsii import check_valid_names
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -52,8 +51,11 @@ def write(
|
||||
DXF does not support shape repetition (only block repeptition). Please call
|
||||
library.wrap_repeated_shapes() before writing to file.
|
||||
|
||||
If you want pattern polygonized with non-default arguments, just call `pattern.polygonize()`
|
||||
prior to calling this function.
|
||||
Other functions you may want to call:
|
||||
- `masque.file.oasis.check_valid_names(library.keys())` to check for invalid names
|
||||
- `library.dangling_references()` to check for references to missing patterns
|
||||
- `pattern.polygonize()` for any patterns with shapes other
|
||||
than `masque.shapes.Polygon` or `masque.shapes.Path`
|
||||
|
||||
Only `Grid` repetition objects with manhattan basis vectors are preserved as arrays. Since DXF
|
||||
rotations apply to basis vectors while `masque`'s rotations do not, the basis vectors of an
|
||||
@ -70,8 +72,6 @@ def write(
|
||||
"""
|
||||
#TODO consider supporting DXF arcs?
|
||||
|
||||
check_valid_names(library.keys())
|
||||
|
||||
pattern = library[top_name]
|
||||
|
||||
# Create library
|
||||
@ -83,7 +83,7 @@ def write(
|
||||
|
||||
# Now create a block for each referenced pattern, and add in any shapes
|
||||
for name, pat in library.items():
|
||||
assert(pat is not None)
|
||||
assert pat is not None
|
||||
block = lib.blocks.new(name=name)
|
||||
|
||||
_shapes_to_elements(block, pat.shapes)
|
||||
@ -173,8 +173,9 @@ def read(
|
||||
msp = lib.modelspace()
|
||||
|
||||
npat = _read_block(msp, clean_vertices)
|
||||
patterns_dict = dict([npat]
|
||||
+ [_read_block(bb, clean_vertices) for bb in lib.blocks if bb.name != '*Model_Space'])
|
||||
patterns_dict = dict(
|
||||
[npat] + [_read_block(bb, clean_vertices) for bb in lib.blocks if bb.name != '*Model_Space']
|
||||
)
|
||||
|
||||
library_info = {
|
||||
'layers': [ll.dxfattribs() for ll in lib.layers]
|
||||
@ -323,8 +324,10 @@ def _shapes_to_elements(
|
||||
# Could set do paths with width setting, but need to consider endcaps.
|
||||
for shape in shapes:
|
||||
if shape.repetition is not None:
|
||||
raise PatternError('Shape repetitions are not supported by DXF.'
|
||||
' Please call library.wrap_repeated_shapes() before writing to file.')
|
||||
raise PatternError(
|
||||
'Shape repetitions are not supported by DXF.'
|
||||
' Please call library.wrap_repeated_shapes() before writing to file.'
|
||||
)
|
||||
|
||||
attribs = {'layer': _mlayer2dxf(shape.layer)}
|
||||
for polygon in shape.to_polygons():
|
||||
|
@ -18,14 +18,10 @@ Notes:
|
||||
* GDS does not support library- or structure-level annotations
|
||||
* Creation/modification/access times are set to 1900-01-01 for reproducibility.
|
||||
"""
|
||||
from typing import List, Any, Dict, Tuple, Callable, Union, Iterable, Optional
|
||||
from typing import Sequence, BinaryIO, Mapping, cast
|
||||
import re
|
||||
from typing import List, Any, Dict, Tuple, Callable, Union, Iterable
|
||||
from typing import BinaryIO, Mapping
|
||||
import io
|
||||
import mmap
|
||||
import copy
|
||||
import base64
|
||||
import struct
|
||||
import logging
|
||||
import pathlib
|
||||
import gzip
|
||||
@ -83,10 +79,13 @@ def write(
|
||||
otherwise `0`
|
||||
|
||||
GDS does not support shape repetition (only cell repeptition). Please call
|
||||
library.wrap_repeated_shapes() before writing to file.
|
||||
`library.wrap_repeated_shapes()` before writing to file.
|
||||
|
||||
If you want pattern polygonized with non-default arguments, just call `pattern.polygonize()`
|
||||
prior to calling this function.
|
||||
Other functions you may want to call:
|
||||
- `masque.file.gdsii.check_valid_names(library.keys())` to check for invalid names
|
||||
- `library.dangling_references()` to check for references to missing patterns
|
||||
- `pattern.polygonize()` for any patterns with shapes other
|
||||
than `masque.shapes.Polygon` or `masque.shapes.Path`
|
||||
|
||||
Args:
|
||||
library: A {name: Pattern} mapping of patterns to write.
|
||||
@ -98,10 +97,6 @@ def write(
|
||||
library_name: Library name written into the GDSII file.
|
||||
Default 'masque-klamath'.
|
||||
"""
|
||||
check_valid_names(library.keys())
|
||||
|
||||
# TODO check all hierarchy present
|
||||
|
||||
if not isinstance(library, MutableLibrary):
|
||||
if isinstance(library, dict):
|
||||
library = WrapLibrary(library)
|
||||
@ -433,7 +428,7 @@ def _shapes_to_elements(
|
||||
for shape in shapes:
|
||||
if shape.repetition is not None:
|
||||
raise PatternError('Shape repetitions are not supported by GDS.'
|
||||
' Please call library.wrap_repeated_shapes() before writing to file.')
|
||||
' Please call library.wrap_repeated_shapes() before writing to file.')
|
||||
|
||||
layer, data_type = _mlayer2gds(shape.layer)
|
||||
properties = _annotations_to_properties(shape.annotations, 128)
|
||||
@ -504,56 +499,6 @@ def _labels_to_texts(labels: List[Label]) -> List[klamath.elements.Text]:
|
||||
return texts
|
||||
|
||||
|
||||
def disambiguate_pattern_names(
|
||||
names: Iterable[str],
|
||||
max_name_length: int = 32,
|
||||
suffix_length: int = 6,
|
||||
) -> List[str]:
|
||||
"""
|
||||
Args:
|
||||
names: List of pattern names to disambiguate
|
||||
max_name_length: Names longer than this will be truncated
|
||||
suffix_length: Names which get truncated are truncated by this many extra characters. This is to
|
||||
leave room for a suffix if one is necessary.
|
||||
"""
|
||||
new_names = []
|
||||
for name in names:
|
||||
# Shorten names which already exceed max-length
|
||||
if len(name) > max_name_length:
|
||||
shortened_name = name[:max_name_length - suffix_length]
|
||||
logger.warning(f'Pattern name "{name}" is too long ({len(name)}/{max_name_length} chars),\n'
|
||||
+ f' shortening to "{shortened_name}" before generating suffix')
|
||||
else:
|
||||
shortened_name = name
|
||||
|
||||
# Remove invalid characters
|
||||
sanitized_name = re.compile(r'[^A-Za-z0-9_\?\$]').sub('_', shortened_name)
|
||||
|
||||
# Add a suffix that makes the name unique
|
||||
i = 0
|
||||
suffixed_name = sanitized_name
|
||||
while suffixed_name in new_names or suffixed_name == '':
|
||||
suffix = base64.b64encode(struct.pack('>Q', i), b'$?').decode('ASCII')
|
||||
|
||||
suffixed_name = sanitized_name + '$' + suffix[:-1].lstrip('A')
|
||||
i += 1
|
||||
|
||||
if sanitized_name == '':
|
||||
logger.warning(f'Empty pattern name saved as "{suffixed_name}"')
|
||||
|
||||
# Encode into a byte-string and perform some final checks
|
||||
encoded_name = suffixed_name.encode('ASCII')
|
||||
if len(encoded_name) == 0:
|
||||
# Should never happen since zero-length names are replaced
|
||||
raise PatternError(f'Zero-length name after sanitize+encode,\n originally "{name}"')
|
||||
if len(encoded_name) > max_name_length:
|
||||
raise PatternError(f'Pattern name "{encoded_name!r}" length > {max_name_length} after encode,\n'
|
||||
+ f' originally "{name}"')
|
||||
|
||||
new_names.append(suffixed_name)
|
||||
return new_names
|
||||
|
||||
|
||||
def load_library(
|
||||
stream: BinaryIO,
|
||||
*,
|
||||
|
@ -12,11 +12,7 @@ Note that OASIS references follow the same convention as `masque`,
|
||||
vectors or offsets.
|
||||
"""
|
||||
from typing import List, Any, Dict, Tuple, Callable, Union, Sequence, Iterable, Mapping, Optional, cast
|
||||
import re
|
||||
import io
|
||||
import copy
|
||||
import base64
|
||||
import struct
|
||||
import logging
|
||||
import pathlib
|
||||
import gzip
|
||||
@ -77,8 +73,11 @@ def build(
|
||||
If a layer map is provided, layer strings will be converted
|
||||
automatically, and layer names will be written to the file.
|
||||
|
||||
If you want pattern polygonized with non-default arguments, just call `pattern.polygonize()`
|
||||
prior to calling this function.
|
||||
Other functions you may want to call:
|
||||
- `masque.file.oasis.check_valid_names(library.keys())` to check for invalid names
|
||||
- `library.dangling_references()` to check for references to missing patterns
|
||||
- `pattern.polygonize()` for any patterns with shapes other
|
||||
than `masque.shapes.Polygon`, `masque.shapes.Path`, or `masque.shapes.Circle`
|
||||
|
||||
Args:
|
||||
library: A {name: Pattern} mapping of patterns to write.
|
||||
@ -97,10 +96,6 @@ def build(
|
||||
Returns:
|
||||
`fatamorgana.OasisLayout`
|
||||
"""
|
||||
check_valid_names(library.keys())
|
||||
|
||||
# TODO check all hierarchy present
|
||||
|
||||
if not isinstance(library, MutableLibrary):
|
||||
if isinstance(library, dict):
|
||||
library = WrapLibrary(library)
|
||||
@ -130,7 +125,7 @@ def build(
|
||||
for tt in (True, False)]
|
||||
|
||||
def layer2oas(mlayer: layer_t) -> Tuple[int, int]:
|
||||
assert(layer_map is not None)
|
||||
assert layer_map is not None
|
||||
layer_num = layer_map[mlayer] if isinstance(mlayer, str) else mlayer
|
||||
return _mlayer2oas(layer_num)
|
||||
else:
|
||||
@ -270,7 +265,7 @@ def read(
|
||||
# note XELEMENT has no repetition
|
||||
continue
|
||||
|
||||
assert(not isinstance(element.repetition, fatamorgana.ReuseRepetition))
|
||||
assert not isinstance(element.repetition, fatamorgana.ReuseRepetition)
|
||||
repetition = repetition_fata2masq(element.repetition)
|
||||
|
||||
# Switch based on element type:
|
||||
@ -481,7 +476,7 @@ def _placement_to_ref(placement: fatrec.Placement, lib: fatamorgana.OasisLayout)
|
||||
"""
|
||||
Helper function to create a Ref from a placment. Sets ref.target to the placement name.
|
||||
"""
|
||||
assert(not isinstance(placement.repetition, fatamorgana.ReuseRepetition))
|
||||
assert not isinstance(placement.repetition, fatamorgana.ReuseRepetition)
|
||||
xy = numpy.array((placement.x, placement.y))
|
||||
mag = placement.magnification if placement.magnification is not None else 1
|
||||
|
||||
@ -659,7 +654,7 @@ def repetition_masq2fata(
|
||||
frep = fatamorgana.ArbitraryRepetition(diff_ints[:, 0], diff_ints[:, 1]) # type: ignore
|
||||
offset = rep.displacements[0, :]
|
||||
else:
|
||||
assert(rep is None)
|
||||
assert rep is None
|
||||
frep = None
|
||||
offset = (0, 0)
|
||||
return frep, offset
|
||||
@ -682,14 +677,14 @@ def properties_to_annotations(
|
||||
) -> annotations_t:
|
||||
annotations = {}
|
||||
for proprec in properties:
|
||||
assert(proprec.name is not None)
|
||||
assert proprec.name is not None
|
||||
if isinstance(proprec.name, int):
|
||||
key = propnames[proprec.name].string
|
||||
else:
|
||||
key = proprec.name.string
|
||||
values: List[Union[str, float, int]] = []
|
||||
|
||||
assert(proprec.values is not None)
|
||||
assert proprec.values is not None
|
||||
for value in proprec.values:
|
||||
if isinstance(value, (float, int)):
|
||||
values.append(value)
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""
|
||||
SVG file format readers and writers
|
||||
"""
|
||||
from typing import Dict, Optional, Mapping
|
||||
from typing import Mapping
|
||||
import warnings
|
||||
|
||||
import numpy
|
||||
|
@ -1,14 +1,11 @@
|
||||
"""
|
||||
Helper functions for file reading and writing
|
||||
"""
|
||||
from typing import Set, Tuple, List, Iterable, Mapping
|
||||
import re
|
||||
import copy
|
||||
import pathlib
|
||||
import logging
|
||||
|
||||
from .. import Pattern, PatternError
|
||||
from ..library import Library, WrapROLibrary
|
||||
from ..shapes import Polygon, Path
|
||||
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
from typing import Tuple, Dict, Optional, TypeVar
|
||||
from typing import Dict, Optional, TypeVar
|
||||
import copy
|
||||
|
||||
import numpy
|
||||
|
@ -3,9 +3,8 @@ Library class for managing unique name->pattern mappings and
|
||||
deferred loading or creation.
|
||||
"""
|
||||
from typing import List, Dict, Callable, TypeVar, Generic, Type, TYPE_CHECKING
|
||||
from typing import Any, Tuple, Union, Iterator, Mapping, MutableMapping, Set, Optional, Sequence
|
||||
from typing import Tuple, Union, Iterator, Mapping, MutableMapping, Set, Optional, Sequence
|
||||
import logging
|
||||
import copy
|
||||
import base64
|
||||
import struct
|
||||
import re
|
||||
@ -14,7 +13,7 @@ from collections import defaultdict
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import numpy
|
||||
from numpy.typing import ArrayLike, NDArray, NDArray
|
||||
from numpy.typing import ArrayLike
|
||||
|
||||
from .error import LibraryError, PatternError
|
||||
from .utils import rotation_matrix_2d, normalize_mirror
|
||||
@ -45,21 +44,51 @@ class Library(Mapping[str, Pattern], metaclass=ABCMeta):
|
||||
def __repr__(self) -> str:
|
||||
return '<Library with keys ' + repr(list(self.keys())) + '>'
|
||||
|
||||
def referenced_patterns(
|
||||
def dangling_references(
|
||||
self,
|
||||
tops: Union[str, Sequence[str]],
|
||||
skip: Optional[Set[Optional[str]]] = None,
|
||||
tops: Union[None, str, Sequence[str]] = None,
|
||||
) -> Set[Optional[str]]:
|
||||
"""
|
||||
Get the set of all pattern names referenced by `top`. Recursively traverses into any refs.
|
||||
Get the set of all pattern names not present in the library but referenced
|
||||
by `tops`, recursively traversing any refs.
|
||||
|
||||
If `tops` are not given, all patterns in the library are checked.
|
||||
|
||||
Args:
|
||||
top: Name of the top pattern(s) to check.
|
||||
tops: Name(s) of the pattern(s) to check.
|
||||
Default is all patterns in the library.
|
||||
skip: Memo, set patterns which have already been traversed.
|
||||
|
||||
Returns:
|
||||
Set of all referenced pattern names
|
||||
"""
|
||||
if tops is None:
|
||||
tops = tuple(self.keys())
|
||||
|
||||
referenced = self.referenced_patterns(tops)
|
||||
return referenced - set(self.keys())
|
||||
|
||||
def referenced_patterns(
|
||||
self,
|
||||
tops: Union[None, str, Sequence[str]] = None,
|
||||
skip: Optional[Set[Optional[str]]] = None,
|
||||
) -> Set[Optional[str]]:
|
||||
"""
|
||||
Get the set of all pattern names referenced by `tops`. Recursively traverses into any refs.
|
||||
|
||||
If `tops` are not given, all patterns in the library are checked.
|
||||
|
||||
Args:
|
||||
tops: Name(s) of the pattern(s) to check.
|
||||
Default is all patterns in the library.
|
||||
skip: Memo, set patterns which have already been traversed.
|
||||
|
||||
Returns:
|
||||
Set of all referenced pattern names
|
||||
"""
|
||||
if tops is None:
|
||||
tops = tuple(self.keys())
|
||||
|
||||
if skip is None:
|
||||
skip = set([None])
|
||||
|
||||
@ -73,17 +102,17 @@ class Library(Mapping[str, Pattern], metaclass=ABCMeta):
|
||||
|
||||
# Perform recursive lookups, but only once for each name
|
||||
for target in targets - skip:
|
||||
assert(target is not None)
|
||||
self.referenced_patterns(target, skip)
|
||||
assert target is not None
|
||||
if target in self:
|
||||
targets |= self.referenced_patterns(target, skip=skip)
|
||||
skip.add(target)
|
||||
|
||||
return targets
|
||||
|
||||
# TODO maybe not for immutable?
|
||||
def subtree(
|
||||
self,
|
||||
tops: Union[str, Sequence[str]],
|
||||
) -> Library:
|
||||
) -> 'Library':
|
||||
"""
|
||||
Return a new `Library`, containing only the specified patterns and the patterns they
|
||||
reference (recursively).
|
||||
@ -184,7 +213,7 @@ class Library(Mapping[str, Pattern], metaclass=ABCMeta):
|
||||
for top in tops:
|
||||
flatten_single(top)
|
||||
|
||||
assert(None not in flattened.values())
|
||||
assert None not in flattened.values()
|
||||
return flattened # type: ignore
|
||||
|
||||
def get_name(
|
||||
@ -364,7 +393,7 @@ class MutableLibrary(Generic[VVV], Library, metaclass=ABCMeta):
|
||||
#def __len__(self) -> int:
|
||||
|
||||
@abstractmethod
|
||||
def __setitem__(self, key: str, value: VVV) -> None: # TODO
|
||||
def __setitem__(self, key: str, value: VVV) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
@ -390,7 +419,7 @@ class MutableLibrary(Generic[VVV], Library, metaclass=ABCMeta):
|
||||
|
||||
Args:
|
||||
other: The library to insert keys from
|
||||
use_ours: Decision function for name conflicts, called with cell name.
|
||||
use_ours: Decision function for name conflicts, called with pattern name.
|
||||
Should return `True` if the value from `self` should be used.
|
||||
use_theirs: Decision function for name conflicts. Same format as `use_ours`.
|
||||
Should return `True` if the value from `other` should be used.
|
||||
@ -451,13 +480,14 @@ class MutableLibrary(Generic[VVV], Library, metaclass=ABCMeta):
|
||||
exclude_types = ()
|
||||
|
||||
if label2name is None:
|
||||
label2name = lambda label: self.get_name('_shape')
|
||||
|
||||
def label2name(label):
|
||||
return self.get_name('_shape_')
|
||||
#label2name = lambda label: self.get_name('_shape')
|
||||
|
||||
shape_counts: MutableMapping[Tuple, int] = defaultdict(int)
|
||||
shape_funcs = {}
|
||||
|
||||
### First pass ###
|
||||
# ## First pass ##
|
||||
# Using the label tuple from `.normalized_form()` as a key, check how many of each shape
|
||||
# are present and store the shape function for each one
|
||||
for pat in tuple(self.values()):
|
||||
@ -476,7 +506,7 @@ class MutableLibrary(Generic[VVV], Library, metaclass=ABCMeta):
|
||||
shape_pat = Pattern(shapes=[shape_func()])
|
||||
shape_pats[label] = shape_pat
|
||||
|
||||
### Second pass ###
|
||||
# ## Second pass ##
|
||||
for pat in tuple(self.values()):
|
||||
# Store `[(index_in_shapes, values_from_normalized_form), ...]` for all shapes which
|
||||
# are to be replaced.
|
||||
@ -534,7 +564,9 @@ class MutableLibrary(Generic[VVV], Library, metaclass=ABCMeta):
|
||||
from .pattern import Pattern
|
||||
|
||||
if name_func is None:
|
||||
name_func = lambda _pat, _shape: self.get_name('_rep')
|
||||
def name_func(_pat, _shape):
|
||||
return self.get_name('_rep_')
|
||||
#name_func = lambda _pat, _shape: self.get_name('_rep')
|
||||
|
||||
for pat in tuple(self.values()):
|
||||
new_shapes = []
|
||||
@ -710,7 +742,7 @@ class LazyLibrary(MutableLibrary):
|
||||
def clear_cache(self: LL) -> LL:
|
||||
"""
|
||||
Clear the cache of this library.
|
||||
This is usually used before modifying or deleting cells, e.g. when merging
|
||||
This is usually used before modifying or deleting patterns, e.g. when merging
|
||||
with another library.
|
||||
|
||||
Returns:
|
||||
|
@ -2,11 +2,10 @@
|
||||
Base object representing a lithography mask.
|
||||
"""
|
||||
|
||||
from typing import List, Callable, Tuple, Dict, Union, Set, Sequence, Optional, Type, overload, cast
|
||||
from typing import Mapping, MutableMapping, Iterable, TypeVar, Any
|
||||
from typing import List, Callable, Dict, Union, Set, Sequence, Optional, cast
|
||||
from typing import Mapping, TypeVar, Any
|
||||
import copy
|
||||
from itertools import chain
|
||||
from collections import defaultdict
|
||||
|
||||
import numpy
|
||||
from numpy import inf
|
||||
@ -14,9 +13,9 @@ from numpy.typing import NDArray, ArrayLike
|
||||
# .visualize imports matplotlib and matplotlib.collections
|
||||
|
||||
from .ref import Ref
|
||||
from .shapes import Shape, Polygon
|
||||
from .shapes import Shape
|
||||
from .label import Label
|
||||
from .utils import rotation_matrix_2d, normalize_mirror, AutoSlots, annotations_t
|
||||
from .utils import rotation_matrix_2d, AutoSlots, annotations_t
|
||||
from .error import PatternError
|
||||
from .traits import AnnotatableImpl, Scalable, Mirrorable, Rotatable, Positionable, Repeatable
|
||||
from .ports import Port, PortList
|
||||
@ -30,7 +29,11 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
2D layout consisting of some set of shapes, labels, and references to other Pattern objects
|
||||
(via Ref). Shapes are assumed to inherit from masque.shapes.Shape or provide equivalent functions.
|
||||
"""
|
||||
__slots__ = ('shapes', 'labels', 'refs', 'ports')
|
||||
__slots__ = (
|
||||
'shapes', 'labels', 'refs', 'ports',
|
||||
# inherited
|
||||
'_offset', '_annotations'
|
||||
)
|
||||
|
||||
shapes: List[Shape]
|
||||
""" List of all shapes in this Pattern.
|
||||
@ -116,7 +119,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
)
|
||||
return new
|
||||
|
||||
def append(self: P, other_pattern: Pattern) -> P:
|
||||
def append(self: P, other_pattern: 'Pattern') -> P:
|
||||
"""
|
||||
Appends all shapes, labels and refs from other_pattern to self's shapes,
|
||||
labels, and supbatterns.
|
||||
@ -321,7 +324,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
`[[x_min, y_min], [x_max, y_max]]`
|
||||
"""
|
||||
bounds = self.get_bounds(library)
|
||||
assert(bounds is not None)
|
||||
assert bounds is not None
|
||||
return bounds
|
||||
|
||||
def translate_elements(self: P, offset: ArrayLike) -> P:
|
||||
|
@ -1,6 +1,5 @@
|
||||
from typing import Dict, Iterable, List, Tuple, Iterator, Optional, Sequence, MutableMapping
|
||||
from typing import overload, KeysView, ValuesView, ItemsView, TYPE_CHECKING, Union, TypeVar, Any
|
||||
import copy
|
||||
from typing import Dict, Iterable, List, Tuple, KeysView, ValuesView, ItemsView
|
||||
from typing import overload, Union, Optional, TypeVar
|
||||
import warnings
|
||||
import traceback
|
||||
import logging
|
||||
@ -14,17 +13,11 @@ from numpy.typing import ArrayLike, NDArray
|
||||
from .traits import PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable
|
||||
from .utils import AutoSlots, rotate_offsets_around
|
||||
from .error import PortError
|
||||
from .library import MutableLibrary
|
||||
from .builder import Tool
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .builder import Builder
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
P = TypeVar('P', bound='Port')
|
||||
PL = TypeVar('PL', bound='PortList')
|
||||
PL2 = TypeVar('PL2', bound='PortList')
|
||||
@ -200,8 +193,6 @@ class PortList(metaclass=ABCMeta):
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
|
||||
|
||||
new_ports = {
|
||||
names[0]: Port(offset, rotation=rotation, ptype=ptype),
|
||||
names[1]: Port(offset, rotation=rotation + pi, ptype=ptype),
|
||||
@ -335,7 +326,6 @@ class PortList(metaclass=ABCMeta):
|
||||
type_conflicts = numpy.array([st != ot and st != 'unk' and ot != 'unk'
|
||||
for st, ot in zip(s_types, o_types)])
|
||||
if type_conflicts.any():
|
||||
ports = numpy.where(type_conflicts)
|
||||
msg = 'Ports have conflicting types:\n'
|
||||
for nn, (k, v) in enumerate(map_in.items()):
|
||||
if type_conflicts[nn]:
|
||||
@ -353,7 +343,7 @@ class PortList(metaclass=ABCMeta):
|
||||
|
||||
if not numpy.allclose(rotations[:1], rotations):
|
||||
rot_deg = numpy.rad2deg(rotations)
|
||||
msg = f'Port orientations do not match:\n'
|
||||
msg = 'Port orientations do not match:\n'
|
||||
for nn, (k, v) in enumerate(map_in.items()):
|
||||
msg += f'{k} | {rot_deg[nn]:g} | {v}\n'
|
||||
raise PortError(msg)
|
||||
@ -362,7 +352,7 @@ class PortList(metaclass=ABCMeta):
|
||||
rotate_offsets_around(o_offsets, pivot, rotations[0])
|
||||
translations = s_offsets - o_offsets
|
||||
if not numpy.allclose(translations[:1], translations):
|
||||
msg = f'Port translations do not match:\n'
|
||||
msg = 'Port translations do not match:\n'
|
||||
for nn, (k, v) in enumerate(map_in.items()):
|
||||
msg += f'{k} | {translations[nn]} | {v}\n'
|
||||
raise PortError(msg)
|
||||
|
@ -4,7 +4,7 @@
|
||||
"""
|
||||
#TODO more top-level documentation
|
||||
|
||||
from typing import Dict, Tuple, Optional, Sequence, Mapping, TYPE_CHECKING, Any, TypeVar
|
||||
from typing import Dict, Optional, Sequence, Mapping, TYPE_CHECKING, Any, TypeVar
|
||||
import copy
|
||||
|
||||
import numpy
|
||||
@ -12,7 +12,7 @@ from numpy import pi
|
||||
from numpy.typing import NDArray, ArrayLike
|
||||
|
||||
from .error import PatternError
|
||||
from .utils import is_scalar, AutoSlots, annotations_t
|
||||
from .utils import is_scalar, annotations_t
|
||||
from .repetition import Repetition
|
||||
from .traits import (
|
||||
PositionableImpl, RotatableImpl, ScalableImpl,
|
||||
@ -109,7 +109,7 @@ class Ref(
|
||||
|
||||
# Mirrored property
|
||||
@property
|
||||
def mirrored(self) -> Any: #TODO mypy#3004 NDArray[numpy.bool_]:
|
||||
def mirrored(self) -> Any: # TODO mypy#3004 NDArray[numpy.bool_]:
|
||||
return self._mirrored
|
||||
|
||||
@mirrored.setter
|
||||
@ -121,8 +121,8 @@ class Ref(
|
||||
def as_pattern(
|
||||
self,
|
||||
*,
|
||||
pattern: Optional[Pattern] = None,
|
||||
library: Optional[Mapping[str, Pattern]] = None,
|
||||
pattern: Optional['Pattern'] = None,
|
||||
library: Optional[Mapping[str, 'Pattern']] = None,
|
||||
) -> 'Pattern':
|
||||
"""
|
||||
Args:
|
||||
@ -138,7 +138,7 @@ class Ref(
|
||||
if library is None:
|
||||
raise PatternError('as_pattern() must be given a pattern or library.')
|
||||
|
||||
assert(self.target is not None)
|
||||
assert self.target is not None
|
||||
pattern = library[self.target]
|
||||
|
||||
pattern = pattern.deepcopy()
|
||||
@ -196,6 +196,8 @@ class Ref(
|
||||
raise PatternError('as_pattern() must be given a pattern or library.')
|
||||
if pattern is None and self.target is None:
|
||||
return None
|
||||
if self.target not in library:
|
||||
raise PatternError(f'get_bounds() called on dangling reference to "{self.target}"')
|
||||
return self.as_pattern(pattern=pattern, library=library).get_bounds()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
|
@ -3,7 +3,7 @@
|
||||
instances of an object .
|
||||
"""
|
||||
|
||||
from typing import Union, Dict, Optional, Sequence, Any, Type
|
||||
from typing import Union, Dict, Optional, Any, Type
|
||||
import copy
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
@ -40,7 +40,7 @@ class Grid(Repetition, metaclass=AutoSlots):
|
||||
Note that the offsets in either the 2D or 1D grids do not have to be axis-aligned.
|
||||
"""
|
||||
__slots__ = (
|
||||
'_a_vector','_b_vector',
|
||||
'_a_vector', '_b_vector',
|
||||
'_a_count', '_b_count',
|
||||
)
|
||||
|
||||
|
@ -48,7 +48,7 @@ class Arc(Shape, metaclass=AutoSlots):
|
||||
|
||||
# radius properties
|
||||
@property
|
||||
def radii(self) -> Any: #TODO mypy#3004 NDArray[numpy.float64]:
|
||||
def radii(self) -> Any: # TODO mypy#3004 NDArray[numpy.float64]:
|
||||
"""
|
||||
Return the radii `[rx, ry]`
|
||||
"""
|
||||
@ -85,7 +85,7 @@ class Arc(Shape, metaclass=AutoSlots):
|
||||
|
||||
# arc start/stop angle properties
|
||||
@property
|
||||
def angles(self) -> Any: #TODO mypy#3004 NDArray[numpy.float64]:
|
||||
def angles(self) -> Any: # TODO mypy#3004 NDArray[numpy.float64]:
|
||||
"""
|
||||
Return the start and stop angles `[a_start, a_stop]`.
|
||||
Angles are measured from x-axis after rotation
|
||||
@ -171,9 +171,9 @@ class Arc(Shape, metaclass=AutoSlots):
|
||||
raw: bool = False,
|
||||
) -> None:
|
||||
if raw:
|
||||
assert(isinstance(radii, numpy.ndarray))
|
||||
assert(isinstance(angles, numpy.ndarray))
|
||||
assert(isinstance(offset, numpy.ndarray))
|
||||
assert isinstance(radii, numpy.ndarray)
|
||||
assert isinstance(angles, numpy.ndarray)
|
||||
assert isinstance(offset, numpy.ndarray)
|
||||
self._radii = radii
|
||||
self._angles = angles
|
||||
self._width = width
|
||||
|
@ -59,7 +59,7 @@ class Circle(Shape, metaclass=AutoSlots):
|
||||
raw: bool = False,
|
||||
) -> None:
|
||||
if raw:
|
||||
assert(isinstance(offset, numpy.ndarray))
|
||||
assert isinstance(offset, numpy.ndarray)
|
||||
self._radius = radius
|
||||
self._offset = offset
|
||||
self._repetition = repetition
|
||||
|
@ -38,7 +38,7 @@ class Ellipse(Shape, metaclass=AutoSlots):
|
||||
|
||||
# radius properties
|
||||
@property
|
||||
def radii(self) -> Any: #TODO mypy#3004 NDArray[numpy.float64]:
|
||||
def radii(self) -> Any: # TODO mypy#3004 NDArray[numpy.float64]:
|
||||
"""
|
||||
Return the radii `[rx, ry]`
|
||||
"""
|
||||
@ -106,8 +106,8 @@ class Ellipse(Shape, metaclass=AutoSlots):
|
||||
raw: bool = False,
|
||||
) -> None:
|
||||
if raw:
|
||||
assert(isinstance(radii, numpy.ndarray))
|
||||
assert(isinstance(offset, numpy.ndarray))
|
||||
assert isinstance(radii, numpy.ndarray)
|
||||
assert isinstance(offset, numpy.ndarray)
|
||||
self._radii = radii
|
||||
self._offset = offset
|
||||
self._rotation = rotation
|
||||
|
@ -76,7 +76,7 @@ class Path(Shape, metaclass=AutoSlots):
|
||||
|
||||
# cap_extensions property
|
||||
@property
|
||||
def cap_extensions(self) -> Optional[Any]: #TODO mypy#3004 NDArray[numpy.float64]]:
|
||||
def cap_extensions(self) -> Optional[Any]: # TODO mypy#3004 NDArray[numpy.float64]]:
|
||||
"""
|
||||
Path end-cap extension
|
||||
|
||||
@ -99,7 +99,7 @@ class Path(Shape, metaclass=AutoSlots):
|
||||
|
||||
# vertices property
|
||||
@property
|
||||
def vertices(self) -> Any: #TODO mypy#3004 NDArray[numpy.float64]]:
|
||||
def vertices(self) -> Any: # TODO mypy#3004 NDArray[numpy.float64]]:
|
||||
"""
|
||||
Vertices of the path (Nx2 ndarray: `[[x0, y0], [x1, y1], ...]`)
|
||||
"""
|
||||
@ -162,9 +162,9 @@ class Path(Shape, metaclass=AutoSlots):
|
||||
self._cap_extensions = None # Since .cap setter might access it
|
||||
|
||||
if raw:
|
||||
assert(isinstance(vertices, numpy.ndarray))
|
||||
assert(isinstance(offset, numpy.ndarray))
|
||||
assert(isinstance(cap_extensions, numpy.ndarray) or cap_extensions is None)
|
||||
assert isinstance(vertices, numpy.ndarray)
|
||||
assert isinstance(offset, numpy.ndarray)
|
||||
assert isinstance(cap_extensions, numpy.ndarray) or cap_extensions is None
|
||||
self._vertices = vertices
|
||||
self._offset = offset
|
||||
self._repetition = repetition
|
||||
@ -229,7 +229,7 @@ class Path(Shape, metaclass=AutoSlots):
|
||||
Returns:
|
||||
The resulting Path object
|
||||
"""
|
||||
#TODO: needs testing
|
||||
# TODO: needs testing
|
||||
direction = numpy.array([1, 0])
|
||||
|
||||
verts = [numpy.zeros(2)]
|
||||
@ -409,7 +409,7 @@ class Path(Shape, metaclass=AutoSlots):
|
||||
if self.cap == PathCap.Square:
|
||||
extensions = numpy.full(2, self.width / 2)
|
||||
elif self.cap == PathCap.SquareCustom:
|
||||
assert(isinstance(self.cap_extensions, numpy.ndarray))
|
||||
assert isinstance(self.cap_extensions, numpy.ndarray)
|
||||
extensions = self.cap_extensions
|
||||
else:
|
||||
# Flush or Circle
|
||||
|
@ -30,7 +30,7 @@ class Polygon(Shape, metaclass=AutoSlots):
|
||||
|
||||
# vertices property
|
||||
@property
|
||||
def vertices(self) -> Any: #TODO mypy#3004 NDArray[numpy.float64]:
|
||||
def vertices(self) -> Any: # TODO mypy#3004 NDArray[numpy.float64]:
|
||||
"""
|
||||
Vertices of the polygon (Nx2 ndarray: `[[x0, y0], [x1, y1], ...]`)
|
||||
"""
|
||||
@ -88,8 +88,8 @@ class Polygon(Shape, metaclass=AutoSlots):
|
||||
raw: bool = False,
|
||||
) -> None:
|
||||
if raw:
|
||||
assert(isinstance(vertices, numpy.ndarray))
|
||||
assert(isinstance(offset, numpy.ndarray))
|
||||
assert isinstance(vertices, numpy.ndarray)
|
||||
assert isinstance(offset, numpy.ndarray)
|
||||
self._vertices = vertices
|
||||
self._offset = offset
|
||||
self._repetition = repetition
|
||||
@ -212,17 +212,17 @@ class Polygon(Shape, metaclass=AutoSlots):
|
||||
"""
|
||||
if lx is None:
|
||||
if xctr is None:
|
||||
assert(xmin is not None)
|
||||
assert(xmax is not None)
|
||||
assert xmin is not None
|
||||
assert xmax is not None
|
||||
xctr = 0.5 * (xmax + xmin)
|
||||
lx = xmax - xmin
|
||||
elif xmax is None:
|
||||
assert(xmin is not None)
|
||||
assert(xctr is not None)
|
||||
assert xmin is not None
|
||||
assert xctr is not None
|
||||
lx = 2 * (xctr - xmin)
|
||||
elif xmin is None:
|
||||
assert(xctr is not None)
|
||||
assert(xmax is not None)
|
||||
assert xctr is not None
|
||||
assert xmax is not None
|
||||
lx = 2 * (xmax - xctr)
|
||||
else:
|
||||
raise PatternError('Two of xmin, xctr, xmax, lx must be None!')
|
||||
@ -230,29 +230,29 @@ class Polygon(Shape, metaclass=AutoSlots):
|
||||
if xctr is not None:
|
||||
pass
|
||||
elif xmax is None:
|
||||
assert(xmin is not None)
|
||||
assert(lx is not None)
|
||||
assert xmin is not None
|
||||
assert lx is not None
|
||||
xctr = xmin + 0.5 * lx
|
||||
elif xmin is None:
|
||||
assert(xmax is not None)
|
||||
assert(lx is not None)
|
||||
assert xmax is not None
|
||||
assert lx is not None
|
||||
xctr = xmax - 0.5 * lx
|
||||
else:
|
||||
raise PatternError('Two of xmin, xctr, xmax, lx must be None!')
|
||||
|
||||
if ly is None:
|
||||
if yctr is None:
|
||||
assert(ymin is not None)
|
||||
assert(ymax is not None)
|
||||
assert ymin is not None
|
||||
assert ymax is not None
|
||||
yctr = 0.5 * (ymax + ymin)
|
||||
ly = ymax - ymin
|
||||
elif ymax is None:
|
||||
assert(ymin is not None)
|
||||
assert(yctr is not None)
|
||||
assert ymin is not None
|
||||
assert yctr is not None
|
||||
ly = 2 * (yctr - ymin)
|
||||
elif ymin is None:
|
||||
assert(yctr is not None)
|
||||
assert(ymax is not None)
|
||||
assert yctr is not None
|
||||
assert ymax is not None
|
||||
ly = 2 * (ymax - yctr)
|
||||
else:
|
||||
raise PatternError('Two of ymin, yctr, ymax, ly must be None!')
|
||||
@ -260,12 +260,12 @@ class Polygon(Shape, metaclass=AutoSlots):
|
||||
if yctr is not None:
|
||||
pass
|
||||
elif ymax is None:
|
||||
assert(ymin is not None)
|
||||
assert(ly is not None)
|
||||
assert ymin is not None
|
||||
assert ly is not None
|
||||
yctr = ymin + 0.5 * ly
|
||||
elif ymin is None:
|
||||
assert(ly is not None)
|
||||
assert(ymax is not None)
|
||||
assert ly is not None
|
||||
assert ymax is not None
|
||||
yctr = ymax - 0.5 * ly
|
||||
else:
|
||||
raise PatternError('Two of ymin, yctr, ymax, ly must be None!')
|
||||
@ -331,7 +331,6 @@ class Polygon(Shape, metaclass=AutoSlots):
|
||||
poly.rotate(rotation)
|
||||
return poly
|
||||
|
||||
|
||||
def to_polygons(
|
||||
self,
|
||||
poly_num_points: Optional[int] = None, # unused
|
||||
|
@ -55,7 +55,7 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
|
||||
|
||||
# Mirrored property
|
||||
@property
|
||||
def mirrored(self) -> Any: #TODO mypy#3004 NDArray[numpy.bool_]:
|
||||
def mirrored(self) -> Any: # TODO mypy#3004 NDArray[numpy.bool_]:
|
||||
return self._mirrored
|
||||
|
||||
@mirrored.setter
|
||||
@ -79,8 +79,8 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
|
||||
raw: bool = False,
|
||||
) -> None:
|
||||
if raw:
|
||||
assert(isinstance(offset, numpy.ndarray))
|
||||
assert(isinstance(mirrored, numpy.ndarray))
|
||||
assert isinstance(offset, numpy.ndarray)
|
||||
assert isinstance(mirrored, numpy.ndarray)
|
||||
self._offset = offset
|
||||
self._layer = layer
|
||||
self._string = string
|
||||
|
@ -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
|
@ -80,7 +80,7 @@ class Positionable(metaclass=ABCMeta):
|
||||
This is handy for destructuring like `xy_min, xy_max = entity.get_bounds_nonempty()`
|
||||
"""
|
||||
bounds = self.get_bounds()
|
||||
assert(bounds is not None)
|
||||
assert bounds is not None
|
||||
return bounds
|
||||
|
||||
|
||||
@ -98,7 +98,7 @@ class PositionableImpl(Positionable, metaclass=ABCMeta):
|
||||
'''
|
||||
# offset property
|
||||
@property
|
||||
def offset(self) -> Any: #TODO mypy#3003 NDArray[numpy.float64]:
|
||||
def offset(self) -> Any: # TODO mypy#3003 NDArray[numpy.float64]:
|
||||
"""
|
||||
[x, y] offset
|
||||
"""
|
||||
|
@ -3,11 +3,11 @@ from abc import ABCMeta, abstractmethod
|
||||
|
||||
import numpy
|
||||
from numpy import pi
|
||||
from numpy.typing import ArrayLike, NDArray
|
||||
from numpy.typing import ArrayLike
|
||||
|
||||
from .positionable import Positionable
|
||||
from ..error import MasqueError
|
||||
from ..utils import is_scalar, rotation_matrix_2d
|
||||
from ..utils import rotation_matrix_2d
|
||||
|
||||
|
||||
_empty_slots = () # Workaround to get mypy to ignore intentionally empty slots for superclass
|
||||
@ -118,7 +118,7 @@ class PivotableImpl(Pivotable, metaclass=ABCMeta):
|
||||
pivot = numpy.array(pivot, dtype=float)
|
||||
cast(Positionable, self).translate(-pivot)
|
||||
cast(Rotatable, self).rotate(rotation)
|
||||
self.offset = numpy.dot(rotation_matrix_2d(rotation), self.offset) #type: ignore #TODO: mypy#3004
|
||||
self.offset = numpy.dot(rotation_matrix_2d(rotation), self.offset) # type: ignore # TODO: mypy#3004
|
||||
cast(Positionable, self).translate(+pivot)
|
||||
return self
|
||||
|
||||
|
@ -11,31 +11,6 @@ from ..pattern import Pattern
|
||||
from ..ref import Ref
|
||||
|
||||
|
||||
def pack_patterns(
|
||||
library: Mapping[str, Pattern],
|
||||
patterns: Sequence[str],
|
||||
regions: numpy.ndarray,
|
||||
spacing: Tuple[float, float],
|
||||
presort: bool = True,
|
||||
allow_rejects: bool = True,
|
||||
packer: Callable = maxrects_bssf,
|
||||
) -> Tuple[Pattern, List[str]]:
|
||||
half_spacing = numpy.array(spacing) / 2
|
||||
|
||||
bounds = [library[pp].get_bounds() for pp in patterns]
|
||||
sizes = [bb[1] - bb[0] + spacing if bb is not None else spacing for bb in bounds]
|
||||
offsets = [half_spacing - bb[0] if bb is not None else (0, 0) for bb in bounds]
|
||||
|
||||
locations, reject_inds = packer(sizes, regions, presort=presort, allow_rejects=allow_rejects)
|
||||
|
||||
pat = Pattern()
|
||||
pat.refs = [Ref(pp, offset=oo + loc)
|
||||
for pp, oo, loc in zip(patterns, offsets, locations)]
|
||||
|
||||
rejects = [patterns[ii] for ii in reject_inds]
|
||||
return pat, rejects
|
||||
|
||||
|
||||
def maxrects_bssf(
|
||||
rects: ArrayLike,
|
||||
containers: ArrayLike,
|
||||
@ -165,3 +140,28 @@ def guillotine_bssf_sas(rect_sizes: numpy.ndarray,
|
||||
new_region0, new_region1))
|
||||
|
||||
return rect_locs, rejected_inds
|
||||
|
||||
|
||||
def pack_patterns(
|
||||
library: Mapping[str, Pattern],
|
||||
patterns: Sequence[str],
|
||||
regions: numpy.ndarray,
|
||||
spacing: Tuple[float, float],
|
||||
presort: bool = True,
|
||||
allow_rejects: bool = True,
|
||||
packer: Callable = maxrects_bssf,
|
||||
) -> Tuple[Pattern, List[str]]:
|
||||
half_spacing = numpy.array(spacing) / 2
|
||||
|
||||
bounds = [library[pp].get_bounds() for pp in patterns]
|
||||
sizes = [bb[1] - bb[0] + spacing if bb is not None else spacing for bb in bounds]
|
||||
offsets = [half_spacing - bb[0] if bb is not None else (0, 0) for bb in bounds]
|
||||
|
||||
locations, reject_inds = packer(sizes, regions, presort=presort, allow_rejects=allow_rejects)
|
||||
|
||||
pat = Pattern()
|
||||
pat.refs = [Ref(pp, offset=oo + loc)
|
||||
for pp, oo, loc in zip(patterns, offsets, locations)]
|
||||
|
||||
rejects = [patterns[ii] for ii in reject_inds]
|
||||
return pat, rejects
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""
|
||||
Type definitions
|
||||
"""
|
||||
from typing import Union, Tuple, Sequence, Dict, List
|
||||
from typing import Union, Tuple, Dict, List
|
||||
|
||||
|
||||
layer_t = Union[int, Tuple[int, int], str]
|
||||
|
@ -83,7 +83,7 @@ def poly_contains_points(
|
||||
max_bounds = numpy.max(vertices, axis=0)[None, :]
|
||||
|
||||
trivially_outside = ((points < min_bounds).any(axis=1)
|
||||
| (points > max_bounds).any(axis=1))
|
||||
| (points > max_bounds).any(axis=1)) # noqa: E128
|
||||
|
||||
nontrivial = ~trivially_outside
|
||||
if trivially_outside.all():
|
||||
@ -101,10 +101,10 @@ def poly_contains_points(
|
||||
|
||||
dv = numpy.roll(verts, -1, axis=0) - verts
|
||||
is_left = (dv[:, 0] * (ntpts[..., 1] - verts[:, 1]) # >0 if left of dv, <0 if right, 0 if on the line
|
||||
- dv[:, 1] * (ntpts[..., 0] - verts[:, 0]))
|
||||
- dv[:, 1] * (ntpts[..., 0] - verts[:, 0])) # noqa: E128
|
||||
|
||||
winding_number = ((upward & (is_left > 0)).sum(axis=0)
|
||||
- (downward & (is_left < 0)).sum(axis=0))
|
||||
- (downward & (is_left < 0)).sum(axis=0)) # noqa: E128
|
||||
|
||||
nontrivial_inside = winding_number != 0 # filter nontrivial points based on winding number
|
||||
if include_boundary:
|
||||
|
Loading…
Reference in New Issue
Block a user