flake8-aided fixes
This commit is contained in:
parent
db9b39dbc0
commit
6b01b43559
@ -15,7 +15,7 @@ to output to multiple formats.
|
|||||||
Requirements:
|
Requirements:
|
||||||
* python >= 3.8
|
* python >= 3.8
|
||||||
* numpy
|
* 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`)
|
* matplotlib (optional, used for `visualization` functions and `text`)
|
||||||
* ezdxf (optional, used for `dxf` i/o)
|
* ezdxf (optional, used for `dxf` i/o)
|
||||||
* fatamorgana (optional, used for `oasis` 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
|
- boolean ops
|
||||||
* Construct polygons from bitmap using `skimage.find_contours`
|
* Construct polygons from bitmap using `skimage.find_contours`
|
||||||
* Deal with shape repetitions for dxf, svg
|
* 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 .error import MasqueError, PatternError, LibraryError, BuildError
|
||||||
from .shapes import Shape
|
from .shapes import Shape, Polygon, Path, Circle, Arc, Ellipse
|
||||||
from .label import Label
|
from .label import Label
|
||||||
from .ref import Ref
|
from .ref import Ref
|
||||||
from .pattern import Pattern
|
from .pattern import Pattern
|
||||||
|
@ -1,17 +1,12 @@
|
|||||||
from typing import Dict, Iterable, List, Tuple, Union, TypeVar, Any, Iterator, Optional, Sequence
|
from typing import Dict, Tuple, Union, TypeVar, Optional, Sequence
|
||||||
from typing import overload, KeysView, ValuesView, MutableMapping, Mapping
|
from typing import MutableMapping, Mapping
|
||||||
import copy
|
import copy
|
||||||
import warnings
|
|
||||||
import traceback
|
|
||||||
import logging
|
import logging
|
||||||
from collections import Counter
|
|
||||||
from abc import ABCMeta
|
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
from numpy import pi
|
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 ..pattern import Pattern
|
||||||
from ..ref import Ref
|
from ..ref import Ref
|
||||||
from ..library import MutableLibrary
|
from ..library import MutableLibrary
|
||||||
@ -241,8 +236,6 @@ class Builder(PortList):
|
|||||||
`PortError` if applying the prefixes results in duplicate port
|
`PortError` if applying the prefixes results in duplicate port
|
||||||
names.
|
names.
|
||||||
"""
|
"""
|
||||||
from ..pattern import Pattern
|
|
||||||
|
|
||||||
if library is None:
|
if library is None:
|
||||||
if hasattr(source, 'library') and isinstance(source, MutableLibrary):
|
if hasattr(source, 'library') and isinstance(source, MutableLibrary):
|
||||||
library = source.library
|
library = source.library
|
||||||
|
@ -82,6 +82,7 @@ def pat2dev(
|
|||||||
|
|
||||||
ports = {}
|
ports = {}
|
||||||
annotated_cells = set()
|
annotated_cells = set()
|
||||||
|
|
||||||
def find_ports_each(pat, hierarchy, transform, memo) -> Pattern:
|
def find_ports_each(pat, hierarchy, transform, memo) -> Pattern:
|
||||||
if len(hierarchy) > max_depth:
|
if len(hierarchy) > max_depth:
|
||||||
if max_depth >= 999_999:
|
if max_depth >= 999_999:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from typing import Dict, Tuple, List, Mapping, Sequence, SupportsFloat
|
from typing import Dict, Mapping, Sequence, SupportsFloat
|
||||||
from typing import Optional, Union, Any, cast, TYPE_CHECKING
|
from typing import Optional, Union, cast, TYPE_CHECKING
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
DXF file format readers and writers
|
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 re
|
||||||
import io
|
import io
|
||||||
import base64
|
import base64
|
||||||
@ -17,7 +17,6 @@ from .. import Pattern, Ref, PatternError, Label, Shape
|
|||||||
from ..shapes import Polygon, Path
|
from ..shapes import Polygon, Path
|
||||||
from ..repetition import Grid
|
from ..repetition import Grid
|
||||||
from ..utils import rotation_matrix_2d, layer_t
|
from ..utils import rotation_matrix_2d, layer_t
|
||||||
from .gdsii import check_valid_names
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -52,8 +51,11 @@ def write(
|
|||||||
DXF does not support shape repetition (only block repeptition). Please call
|
DXF does not support shape repetition (only block 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()`
|
Other functions you may want to call:
|
||||||
prior to calling this function.
|
- `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
|
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
|
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?
|
#TODO consider supporting DXF arcs?
|
||||||
|
|
||||||
check_valid_names(library.keys())
|
|
||||||
|
|
||||||
pattern = library[top_name]
|
pattern = library[top_name]
|
||||||
|
|
||||||
# Create library
|
# Create library
|
||||||
@ -83,7 +83,7 @@ def write(
|
|||||||
|
|
||||||
# Now create a block for each referenced pattern, and add in any shapes
|
# Now create a block for each referenced pattern, and add in any shapes
|
||||||
for name, pat in library.items():
|
for name, pat in library.items():
|
||||||
assert(pat is not None)
|
assert pat is not None
|
||||||
block = lib.blocks.new(name=name)
|
block = lib.blocks.new(name=name)
|
||||||
|
|
||||||
_shapes_to_elements(block, pat.shapes)
|
_shapes_to_elements(block, pat.shapes)
|
||||||
@ -173,8 +173,9 @@ def read(
|
|||||||
msp = lib.modelspace()
|
msp = lib.modelspace()
|
||||||
|
|
||||||
npat = _read_block(msp, clean_vertices)
|
npat = _read_block(msp, clean_vertices)
|
||||||
patterns_dict = dict([npat]
|
patterns_dict = dict(
|
||||||
+ [_read_block(bb, clean_vertices) for bb in lib.blocks if bb.name != '*Model_Space'])
|
[npat] + [_read_block(bb, clean_vertices) for bb in lib.blocks if bb.name != '*Model_Space']
|
||||||
|
)
|
||||||
|
|
||||||
library_info = {
|
library_info = {
|
||||||
'layers': [ll.dxfattribs() for ll in lib.layers]
|
'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.
|
# Could set do paths with width setting, but need to consider endcaps.
|
||||||
for shape in shapes:
|
for shape in shapes:
|
||||||
if shape.repetition is not None:
|
if shape.repetition is not None:
|
||||||
raise PatternError('Shape repetitions are not supported by DXF.'
|
raise PatternError(
|
||||||
' Please call library.wrap_repeated_shapes() before writing to file.')
|
'Shape repetitions are not supported by DXF.'
|
||||||
|
' Please call library.wrap_repeated_shapes() before writing to file.'
|
||||||
|
)
|
||||||
|
|
||||||
attribs = {'layer': _mlayer2dxf(shape.layer)}
|
attribs = {'layer': _mlayer2dxf(shape.layer)}
|
||||||
for polygon in shape.to_polygons():
|
for polygon in shape.to_polygons():
|
||||||
|
@ -18,14 +18,10 @@ Notes:
|
|||||||
* GDS does not support library- or structure-level annotations
|
* GDS does not support library- or structure-level annotations
|
||||||
* Creation/modification/access times are set to 1900-01-01 for reproducibility.
|
* 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 List, Any, Dict, Tuple, Callable, Union, Iterable
|
||||||
from typing import Sequence, BinaryIO, Mapping, cast
|
from typing import BinaryIO, Mapping
|
||||||
import re
|
|
||||||
import io
|
import io
|
||||||
import mmap
|
import mmap
|
||||||
import copy
|
|
||||||
import base64
|
|
||||||
import struct
|
|
||||||
import logging
|
import logging
|
||||||
import pathlib
|
import pathlib
|
||||||
import gzip
|
import gzip
|
||||||
@ -83,10 +79,13 @@ def write(
|
|||||||
otherwise `0`
|
otherwise `0`
|
||||||
|
|
||||||
GDS does not support shape repetition (only cell repeptition). Please call
|
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()`
|
Other functions you may want to call:
|
||||||
prior to calling this function.
|
- `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:
|
Args:
|
||||||
library: A {name: Pattern} mapping of patterns to write.
|
library: A {name: Pattern} mapping of patterns to write.
|
||||||
@ -98,10 +97,6 @@ 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'.
|
||||||
"""
|
"""
|
||||||
check_valid_names(library.keys())
|
|
||||||
|
|
||||||
# TODO check all hierarchy present
|
|
||||||
|
|
||||||
if not isinstance(library, MutableLibrary):
|
if not isinstance(library, MutableLibrary):
|
||||||
if isinstance(library, dict):
|
if isinstance(library, dict):
|
||||||
library = WrapLibrary(library)
|
library = WrapLibrary(library)
|
||||||
@ -504,56 +499,6 @@ def _labels_to_texts(labels: List[Label]) -> List[klamath.elements.Text]:
|
|||||||
return texts
|
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(
|
def load_library(
|
||||||
stream: BinaryIO,
|
stream: BinaryIO,
|
||||||
*,
|
*,
|
||||||
|
@ -12,11 +12,7 @@ Note that OASIS references follow the same convention as `masque`,
|
|||||||
vectors or offsets.
|
vectors or offsets.
|
||||||
"""
|
"""
|
||||||
from typing import List, Any, Dict, Tuple, Callable, Union, Sequence, Iterable, Mapping, Optional, cast
|
from typing import List, Any, Dict, Tuple, Callable, Union, Sequence, Iterable, Mapping, Optional, cast
|
||||||
import re
|
|
||||||
import io
|
import io
|
||||||
import copy
|
|
||||||
import base64
|
|
||||||
import struct
|
|
||||||
import logging
|
import logging
|
||||||
import pathlib
|
import pathlib
|
||||||
import gzip
|
import gzip
|
||||||
@ -77,8 +73,11 @@ def build(
|
|||||||
If a layer map is provided, layer strings will be converted
|
If a layer map is provided, layer strings will be converted
|
||||||
automatically, and layer names will be written to the file.
|
automatically, and layer names will be written to the file.
|
||||||
|
|
||||||
If you want pattern polygonized with non-default arguments, just call `pattern.polygonize()`
|
Other functions you may want to call:
|
||||||
prior to calling this function.
|
- `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:
|
Args:
|
||||||
library: A {name: Pattern} mapping of patterns to write.
|
library: A {name: Pattern} mapping of patterns to write.
|
||||||
@ -97,10 +96,6 @@ def build(
|
|||||||
Returns:
|
Returns:
|
||||||
`fatamorgana.OasisLayout`
|
`fatamorgana.OasisLayout`
|
||||||
"""
|
"""
|
||||||
check_valid_names(library.keys())
|
|
||||||
|
|
||||||
# TODO check all hierarchy present
|
|
||||||
|
|
||||||
if not isinstance(library, MutableLibrary):
|
if not isinstance(library, MutableLibrary):
|
||||||
if isinstance(library, dict):
|
if isinstance(library, dict):
|
||||||
library = WrapLibrary(library)
|
library = WrapLibrary(library)
|
||||||
@ -130,7 +125,7 @@ def build(
|
|||||||
for tt in (True, False)]
|
for tt in (True, False)]
|
||||||
|
|
||||||
def layer2oas(mlayer: layer_t) -> Tuple[int, int]:
|
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
|
layer_num = layer_map[mlayer] if isinstance(mlayer, str) else mlayer
|
||||||
return _mlayer2oas(layer_num)
|
return _mlayer2oas(layer_num)
|
||||||
else:
|
else:
|
||||||
@ -270,7 +265,7 @@ def read(
|
|||||||
# note XELEMENT has no repetition
|
# note XELEMENT has no repetition
|
||||||
continue
|
continue
|
||||||
|
|
||||||
assert(not isinstance(element.repetition, fatamorgana.ReuseRepetition))
|
assert not isinstance(element.repetition, fatamorgana.ReuseRepetition)
|
||||||
repetition = repetition_fata2masq(element.repetition)
|
repetition = repetition_fata2masq(element.repetition)
|
||||||
|
|
||||||
# Switch based on element type:
|
# 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.
|
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))
|
xy = numpy.array((placement.x, placement.y))
|
||||||
mag = placement.magnification if placement.magnification is not None else 1
|
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
|
frep = fatamorgana.ArbitraryRepetition(diff_ints[:, 0], diff_ints[:, 1]) # type: ignore
|
||||||
offset = rep.displacements[0, :]
|
offset = rep.displacements[0, :]
|
||||||
else:
|
else:
|
||||||
assert(rep is None)
|
assert rep is None
|
||||||
frep = None
|
frep = None
|
||||||
offset = (0, 0)
|
offset = (0, 0)
|
||||||
return frep, offset
|
return frep, offset
|
||||||
@ -682,14 +677,14 @@ def properties_to_annotations(
|
|||||||
) -> annotations_t:
|
) -> annotations_t:
|
||||||
annotations = {}
|
annotations = {}
|
||||||
for proprec in properties:
|
for proprec in properties:
|
||||||
assert(proprec.name is not None)
|
assert proprec.name is not None
|
||||||
if isinstance(proprec.name, int):
|
if isinstance(proprec.name, int):
|
||||||
key = propnames[proprec.name].string
|
key = propnames[proprec.name].string
|
||||||
else:
|
else:
|
||||||
key = proprec.name.string
|
key = proprec.name.string
|
||||||
values: List[Union[str, float, int]] = []
|
values: List[Union[str, float, int]] = []
|
||||||
|
|
||||||
assert(proprec.values is not None)
|
assert proprec.values is not None
|
||||||
for value in proprec.values:
|
for value in proprec.values:
|
||||||
if isinstance(value, (float, int)):
|
if isinstance(value, (float, int)):
|
||||||
values.append(value)
|
values.append(value)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
SVG file format readers and writers
|
SVG file format readers and writers
|
||||||
"""
|
"""
|
||||||
from typing import Dict, Optional, Mapping
|
from typing import Mapping
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
"""
|
"""
|
||||||
Helper functions for file reading and writing
|
Helper functions for file reading and writing
|
||||||
"""
|
"""
|
||||||
from typing import Set, Tuple, List, Iterable, Mapping
|
|
||||||
import re
|
import re
|
||||||
import copy
|
|
||||||
import pathlib
|
import pathlib
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .. import Pattern, PatternError
|
from .. import Pattern, PatternError
|
||||||
from ..library import Library, WrapROLibrary
|
|
||||||
from ..shapes import Polygon, Path
|
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 copy
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
|
@ -3,9 +3,8 @@ Library class for managing unique name->pattern mappings and
|
|||||||
deferred loading or creation.
|
deferred loading or creation.
|
||||||
"""
|
"""
|
||||||
from typing import List, Dict, Callable, TypeVar, Generic, Type, TYPE_CHECKING
|
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 logging
|
||||||
import copy
|
|
||||||
import base64
|
import base64
|
||||||
import struct
|
import struct
|
||||||
import re
|
import re
|
||||||
@ -14,7 +13,7 @@ from collections import defaultdict
|
|||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
from numpy.typing import ArrayLike, NDArray, NDArray
|
from numpy.typing import ArrayLike
|
||||||
|
|
||||||
from .error import LibraryError, PatternError
|
from .error import LibraryError, PatternError
|
||||||
from .utils import rotation_matrix_2d, normalize_mirror
|
from .utils import rotation_matrix_2d, normalize_mirror
|
||||||
@ -45,21 +44,51 @@ class Library(Mapping[str, Pattern], metaclass=ABCMeta):
|
|||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return '<Library with keys ' + repr(list(self.keys())) + '>'
|
return '<Library with keys ' + repr(list(self.keys())) + '>'
|
||||||
|
|
||||||
def referenced_patterns(
|
def dangling_references(
|
||||||
self,
|
self,
|
||||||
tops: Union[str, Sequence[str]],
|
tops: Union[None, str, Sequence[str]] = None,
|
||||||
skip: Optional[Set[Optional[str]]] = None,
|
|
||||||
) -> Set[Optional[str]]:
|
) -> 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:
|
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.
|
skip: Memo, set patterns which have already been traversed.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Set of all referenced pattern names
|
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:
|
if skip is None:
|
||||||
skip = set([None])
|
skip = set([None])
|
||||||
|
|
||||||
@ -73,17 +102,17 @@ class Library(Mapping[str, Pattern], metaclass=ABCMeta):
|
|||||||
|
|
||||||
# Perform recursive lookups, but only once for each name
|
# Perform recursive lookups, but only once for each name
|
||||||
for target in targets - skip:
|
for target in targets - skip:
|
||||||
assert(target is not None)
|
assert target is not None
|
||||||
self.referenced_patterns(target, skip)
|
if target in self:
|
||||||
|
targets |= self.referenced_patterns(target, skip=skip)
|
||||||
skip.add(target)
|
skip.add(target)
|
||||||
|
|
||||||
return targets
|
return targets
|
||||||
|
|
||||||
# TODO maybe not for immutable?
|
|
||||||
def subtree(
|
def subtree(
|
||||||
self,
|
self,
|
||||||
tops: Union[str, Sequence[str]],
|
tops: Union[str, Sequence[str]],
|
||||||
) -> Library:
|
) -> 'Library':
|
||||||
"""
|
"""
|
||||||
Return a new `Library`, containing only the specified patterns and the patterns they
|
Return a new `Library`, containing only the specified patterns and the patterns they
|
||||||
reference (recursively).
|
reference (recursively).
|
||||||
@ -184,7 +213,7 @@ class Library(Mapping[str, Pattern], metaclass=ABCMeta):
|
|||||||
for top in tops:
|
for top in tops:
|
||||||
flatten_single(top)
|
flatten_single(top)
|
||||||
|
|
||||||
assert(None not in flattened.values())
|
assert None not in flattened.values()
|
||||||
return flattened # type: ignore
|
return flattened # type: ignore
|
||||||
|
|
||||||
def get_name(
|
def get_name(
|
||||||
@ -364,7 +393,7 @@ class MutableLibrary(Generic[VVV], Library, metaclass=ABCMeta):
|
|||||||
#def __len__(self) -> int:
|
#def __len__(self) -> int:
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def __setitem__(self, key: str, value: VVV) -> None: # TODO
|
def __setitem__(self, key: str, value: VVV) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@ -390,7 +419,7 @@ class MutableLibrary(Generic[VVV], Library, metaclass=ABCMeta):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
other: The library to insert keys from
|
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.
|
Should return `True` if the value from `self` should be used.
|
||||||
use_theirs: Decision function for name conflicts. Same format as `use_ours`.
|
use_theirs: Decision function for name conflicts. Same format as `use_ours`.
|
||||||
Should return `True` if the value from `other` should be used.
|
Should return `True` if the value from `other` should be used.
|
||||||
@ -451,13 +480,14 @@ class MutableLibrary(Generic[VVV], Library, metaclass=ABCMeta):
|
|||||||
exclude_types = ()
|
exclude_types = ()
|
||||||
|
|
||||||
if label2name is None:
|
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_counts: MutableMapping[Tuple, int] = defaultdict(int)
|
||||||
shape_funcs = {}
|
shape_funcs = {}
|
||||||
|
|
||||||
### First pass ###
|
# ## First pass ##
|
||||||
# Using the label tuple from `.normalized_form()` as a key, check how many of each shape
|
# 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
|
# are present and store the shape function for each one
|
||||||
for pat in tuple(self.values()):
|
for pat in tuple(self.values()):
|
||||||
@ -476,7 +506,7 @@ class MutableLibrary(Generic[VVV], Library, metaclass=ABCMeta):
|
|||||||
shape_pat = Pattern(shapes=[shape_func()])
|
shape_pat = Pattern(shapes=[shape_func()])
|
||||||
shape_pats[label] = shape_pat
|
shape_pats[label] = shape_pat
|
||||||
|
|
||||||
### Second pass ###
|
# ## Second pass ##
|
||||||
for pat in tuple(self.values()):
|
for pat in tuple(self.values()):
|
||||||
# Store `[(index_in_shapes, values_from_normalized_form), ...]` for all shapes which
|
# Store `[(index_in_shapes, values_from_normalized_form), ...]` for all shapes which
|
||||||
# are to be replaced.
|
# are to be replaced.
|
||||||
@ -534,7 +564,9 @@ class MutableLibrary(Generic[VVV], Library, metaclass=ABCMeta):
|
|||||||
from .pattern import Pattern
|
from .pattern import Pattern
|
||||||
|
|
||||||
if name_func is None:
|
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()):
|
for pat in tuple(self.values()):
|
||||||
new_shapes = []
|
new_shapes = []
|
||||||
@ -710,7 +742,7 @@ class LazyLibrary(MutableLibrary):
|
|||||||
def clear_cache(self: LL) -> LL:
|
def clear_cache(self: LL) -> LL:
|
||||||
"""
|
"""
|
||||||
Clear the cache of this library.
|
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.
|
with another library.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
@ -2,11 +2,10 @@
|
|||||||
Base object representing a lithography mask.
|
Base object representing a lithography mask.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import List, Callable, Tuple, Dict, Union, Set, Sequence, Optional, Type, overload, cast
|
from typing import List, Callable, Dict, Union, Set, Sequence, Optional, cast
|
||||||
from typing import Mapping, MutableMapping, Iterable, TypeVar, Any
|
from typing import Mapping, TypeVar, Any
|
||||||
import copy
|
import copy
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
from numpy import inf
|
from numpy import inf
|
||||||
@ -14,9 +13,9 @@ from numpy.typing import NDArray, ArrayLike
|
|||||||
# .visualize imports matplotlib and matplotlib.collections
|
# .visualize imports matplotlib and matplotlib.collections
|
||||||
|
|
||||||
from .ref import Ref
|
from .ref import Ref
|
||||||
from .shapes import Shape, Polygon
|
from .shapes import Shape
|
||||||
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, AutoSlots, annotations_t
|
||||||
from .error import PatternError
|
from .error import PatternError
|
||||||
from .traits import AnnotatableImpl, Scalable, Mirrorable, Rotatable, Positionable, Repeatable
|
from .traits import AnnotatableImpl, Scalable, Mirrorable, Rotatable, Positionable, Repeatable
|
||||||
from .ports import Port, PortList
|
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
|
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.
|
(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]
|
shapes: List[Shape]
|
||||||
""" List of all shapes in this Pattern.
|
""" List of all shapes in this Pattern.
|
||||||
@ -116,7 +119,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
|||||||
)
|
)
|
||||||
return new
|
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,
|
Appends all shapes, labels and refs from other_pattern to self's shapes,
|
||||||
labels, and supbatterns.
|
labels, and supbatterns.
|
||||||
@ -321,7 +324,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
|||||||
`[[x_min, y_min], [x_max, y_max]]`
|
`[[x_min, y_min], [x_max, y_max]]`
|
||||||
"""
|
"""
|
||||||
bounds = self.get_bounds(library)
|
bounds = self.get_bounds(library)
|
||||||
assert(bounds is not None)
|
assert bounds is not None
|
||||||
return bounds
|
return bounds
|
||||||
|
|
||||||
def translate_elements(self: P, offset: ArrayLike) -> P:
|
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 Dict, Iterable, List, Tuple, KeysView, ValuesView, ItemsView
|
||||||
from typing import overload, KeysView, ValuesView, ItemsView, TYPE_CHECKING, Union, TypeVar, Any
|
from typing import overload, Union, Optional, TypeVar
|
||||||
import copy
|
|
||||||
import warnings
|
import warnings
|
||||||
import traceback
|
import traceback
|
||||||
import logging
|
import logging
|
||||||
@ -14,17 +13,11 @@ from numpy.typing import ArrayLike, NDArray
|
|||||||
from .traits import PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable
|
from .traits import PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable
|
||||||
from .utils import AutoSlots, rotate_offsets_around
|
from .utils import AutoSlots, rotate_offsets_around
|
||||||
from .error import PortError
|
from .error import PortError
|
||||||
from .library import MutableLibrary
|
|
||||||
from .builder import Tool
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from .builder import Builder
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
P = TypeVar('P', bound='Port')
|
P = TypeVar('P', bound='Port')
|
||||||
PL = TypeVar('PL', bound='PortList')
|
PL = TypeVar('PL', bound='PortList')
|
||||||
PL2 = TypeVar('PL2', bound='PortList')
|
PL2 = TypeVar('PL2', bound='PortList')
|
||||||
@ -200,8 +193,6 @@ class PortList(metaclass=ABCMeta):
|
|||||||
Returns:
|
Returns:
|
||||||
self
|
self
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
new_ports = {
|
new_ports = {
|
||||||
names[0]: Port(offset, rotation=rotation, ptype=ptype),
|
names[0]: Port(offset, rotation=rotation, ptype=ptype),
|
||||||
names[1]: Port(offset, rotation=rotation + pi, 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'
|
type_conflicts = numpy.array([st != ot and st != 'unk' and ot != 'unk'
|
||||||
for st, ot in zip(s_types, o_types)])
|
for st, ot in zip(s_types, o_types)])
|
||||||
if type_conflicts.any():
|
if type_conflicts.any():
|
||||||
ports = numpy.where(type_conflicts)
|
|
||||||
msg = 'Ports have conflicting types:\n'
|
msg = 'Ports have conflicting types:\n'
|
||||||
for nn, (k, v) in enumerate(map_in.items()):
|
for nn, (k, v) in enumerate(map_in.items()):
|
||||||
if type_conflicts[nn]:
|
if type_conflicts[nn]:
|
||||||
@ -353,7 +343,7 @@ class PortList(metaclass=ABCMeta):
|
|||||||
|
|
||||||
if not numpy.allclose(rotations[:1], rotations):
|
if not numpy.allclose(rotations[:1], rotations):
|
||||||
rot_deg = numpy.rad2deg(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()):
|
for nn, (k, v) in enumerate(map_in.items()):
|
||||||
msg += f'{k} | {rot_deg[nn]:g} | {v}\n'
|
msg += f'{k} | {rot_deg[nn]:g} | {v}\n'
|
||||||
raise PortError(msg)
|
raise PortError(msg)
|
||||||
@ -362,7 +352,7 @@ class PortList(metaclass=ABCMeta):
|
|||||||
rotate_offsets_around(o_offsets, pivot, rotations[0])
|
rotate_offsets_around(o_offsets, pivot, rotations[0])
|
||||||
translations = s_offsets - o_offsets
|
translations = s_offsets - o_offsets
|
||||||
if not numpy.allclose(translations[:1], translations):
|
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()):
|
for nn, (k, v) in enumerate(map_in.items()):
|
||||||
msg += f'{k} | {translations[nn]} | {v}\n'
|
msg += f'{k} | {translations[nn]} | {v}\n'
|
||||||
raise PortError(msg)
|
raise PortError(msg)
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"""
|
"""
|
||||||
#TODO more top-level documentation
|
#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 copy
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
@ -12,7 +12,7 @@ from numpy import pi
|
|||||||
from numpy.typing import NDArray, ArrayLike
|
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, annotations_t
|
||||||
from .repetition import Repetition
|
from .repetition import Repetition
|
||||||
from .traits import (
|
from .traits import (
|
||||||
PositionableImpl, RotatableImpl, ScalableImpl,
|
PositionableImpl, RotatableImpl, ScalableImpl,
|
||||||
@ -121,8 +121,8 @@ class Ref(
|
|||||||
def as_pattern(
|
def as_pattern(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
pattern: Optional[Pattern] = None,
|
pattern: Optional['Pattern'] = None,
|
||||||
library: Optional[Mapping[str, Pattern]] = None,
|
library: Optional[Mapping[str, 'Pattern']] = None,
|
||||||
) -> 'Pattern':
|
) -> 'Pattern':
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
@ -138,7 +138,7 @@ class Ref(
|
|||||||
if library is None:
|
if library is None:
|
||||||
raise PatternError('as_pattern() must be given a pattern or library.')
|
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 = library[self.target]
|
||||||
|
|
||||||
pattern = pattern.deepcopy()
|
pattern = pattern.deepcopy()
|
||||||
@ -196,6 +196,8 @@ class Ref(
|
|||||||
raise PatternError('as_pattern() must be given a pattern or library.')
|
raise PatternError('as_pattern() must be given a pattern or library.')
|
||||||
if pattern is None and self.target is None:
|
if pattern is None and self.target is None:
|
||||||
return 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()
|
return self.as_pattern(pattern=pattern, library=library).get_bounds()
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
instances of an object .
|
instances of an object .
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Union, Dict, Optional, Sequence, Any, Type
|
from typing import Union, Dict, Optional, Any, Type
|
||||||
import copy
|
import copy
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
|
|
||||||
|
@ -171,9 +171,9 @@ class Arc(Shape, metaclass=AutoSlots):
|
|||||||
raw: bool = False,
|
raw: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
if raw:
|
if raw:
|
||||||
assert(isinstance(radii, numpy.ndarray))
|
assert isinstance(radii, numpy.ndarray)
|
||||||
assert(isinstance(angles, numpy.ndarray))
|
assert isinstance(angles, numpy.ndarray)
|
||||||
assert(isinstance(offset, numpy.ndarray))
|
assert isinstance(offset, numpy.ndarray)
|
||||||
self._radii = radii
|
self._radii = radii
|
||||||
self._angles = angles
|
self._angles = angles
|
||||||
self._width = width
|
self._width = width
|
||||||
|
@ -59,7 +59,7 @@ class Circle(Shape, metaclass=AutoSlots):
|
|||||||
raw: bool = False,
|
raw: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
if raw:
|
if raw:
|
||||||
assert(isinstance(offset, numpy.ndarray))
|
assert isinstance(offset, numpy.ndarray)
|
||||||
self._radius = radius
|
self._radius = radius
|
||||||
self._offset = offset
|
self._offset = offset
|
||||||
self._repetition = repetition
|
self._repetition = repetition
|
||||||
|
@ -106,8 +106,8 @@ class Ellipse(Shape, metaclass=AutoSlots):
|
|||||||
raw: bool = False,
|
raw: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
if raw:
|
if raw:
|
||||||
assert(isinstance(radii, numpy.ndarray))
|
assert isinstance(radii, numpy.ndarray)
|
||||||
assert(isinstance(offset, numpy.ndarray))
|
assert isinstance(offset, numpy.ndarray)
|
||||||
self._radii = radii
|
self._radii = radii
|
||||||
self._offset = offset
|
self._offset = offset
|
||||||
self._rotation = rotation
|
self._rotation = rotation
|
||||||
|
@ -162,9 +162,9 @@ class Path(Shape, metaclass=AutoSlots):
|
|||||||
self._cap_extensions = None # Since .cap setter might access it
|
self._cap_extensions = None # Since .cap setter might access it
|
||||||
|
|
||||||
if raw:
|
if raw:
|
||||||
assert(isinstance(vertices, numpy.ndarray))
|
assert isinstance(vertices, numpy.ndarray)
|
||||||
assert(isinstance(offset, numpy.ndarray))
|
assert isinstance(offset, numpy.ndarray)
|
||||||
assert(isinstance(cap_extensions, numpy.ndarray) or cap_extensions is None)
|
assert isinstance(cap_extensions, numpy.ndarray) or cap_extensions is None
|
||||||
self._vertices = vertices
|
self._vertices = vertices
|
||||||
self._offset = offset
|
self._offset = offset
|
||||||
self._repetition = repetition
|
self._repetition = repetition
|
||||||
@ -409,7 +409,7 @@ class Path(Shape, metaclass=AutoSlots):
|
|||||||
if self.cap == PathCap.Square:
|
if self.cap == PathCap.Square:
|
||||||
extensions = numpy.full(2, self.width / 2)
|
extensions = numpy.full(2, self.width / 2)
|
||||||
elif self.cap == PathCap.SquareCustom:
|
elif self.cap == PathCap.SquareCustom:
|
||||||
assert(isinstance(self.cap_extensions, numpy.ndarray))
|
assert isinstance(self.cap_extensions, numpy.ndarray)
|
||||||
extensions = self.cap_extensions
|
extensions = self.cap_extensions
|
||||||
else:
|
else:
|
||||||
# Flush or Circle
|
# Flush or Circle
|
||||||
|
@ -88,8 +88,8 @@ class Polygon(Shape, metaclass=AutoSlots):
|
|||||||
raw: bool = False,
|
raw: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
if raw:
|
if raw:
|
||||||
assert(isinstance(vertices, numpy.ndarray))
|
assert isinstance(vertices, numpy.ndarray)
|
||||||
assert(isinstance(offset, numpy.ndarray))
|
assert isinstance(offset, numpy.ndarray)
|
||||||
self._vertices = vertices
|
self._vertices = vertices
|
||||||
self._offset = offset
|
self._offset = offset
|
||||||
self._repetition = repetition
|
self._repetition = repetition
|
||||||
@ -212,17 +212,17 @@ class Polygon(Shape, metaclass=AutoSlots):
|
|||||||
"""
|
"""
|
||||||
if lx is None:
|
if lx is None:
|
||||||
if xctr is None:
|
if xctr is None:
|
||||||
assert(xmin is not None)
|
assert xmin is not None
|
||||||
assert(xmax is not None)
|
assert xmax is not None
|
||||||
xctr = 0.5 * (xmax + xmin)
|
xctr = 0.5 * (xmax + xmin)
|
||||||
lx = xmax - xmin
|
lx = xmax - xmin
|
||||||
elif xmax is None:
|
elif xmax is None:
|
||||||
assert(xmin is not None)
|
assert xmin is not None
|
||||||
assert(xctr is not None)
|
assert xctr is not None
|
||||||
lx = 2 * (xctr - xmin)
|
lx = 2 * (xctr - xmin)
|
||||||
elif xmin is None:
|
elif xmin is None:
|
||||||
assert(xctr is not None)
|
assert xctr is not None
|
||||||
assert(xmax is not None)
|
assert xmax is not None
|
||||||
lx = 2 * (xmax - xctr)
|
lx = 2 * (xmax - xctr)
|
||||||
else:
|
else:
|
||||||
raise PatternError('Two of xmin, xctr, xmax, lx must be None!')
|
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:
|
if xctr is not None:
|
||||||
pass
|
pass
|
||||||
elif xmax is None:
|
elif xmax is None:
|
||||||
assert(xmin is not None)
|
assert xmin is not None
|
||||||
assert(lx is not None)
|
assert lx is not None
|
||||||
xctr = xmin + 0.5 * lx
|
xctr = xmin + 0.5 * lx
|
||||||
elif xmin is None:
|
elif xmin is None:
|
||||||
assert(xmax is not None)
|
assert xmax is not None
|
||||||
assert(lx is not None)
|
assert lx is not None
|
||||||
xctr = xmax - 0.5 * lx
|
xctr = xmax - 0.5 * lx
|
||||||
else:
|
else:
|
||||||
raise PatternError('Two of xmin, xctr, xmax, lx must be None!')
|
raise PatternError('Two of xmin, xctr, xmax, lx must be None!')
|
||||||
|
|
||||||
if ly is None:
|
if ly is None:
|
||||||
if yctr is None:
|
if yctr is None:
|
||||||
assert(ymin is not None)
|
assert ymin is not None
|
||||||
assert(ymax is not None)
|
assert ymax is not None
|
||||||
yctr = 0.5 * (ymax + ymin)
|
yctr = 0.5 * (ymax + ymin)
|
||||||
ly = ymax - ymin
|
ly = ymax - ymin
|
||||||
elif ymax is None:
|
elif ymax is None:
|
||||||
assert(ymin is not None)
|
assert ymin is not None
|
||||||
assert(yctr is not None)
|
assert yctr is not None
|
||||||
ly = 2 * (yctr - ymin)
|
ly = 2 * (yctr - ymin)
|
||||||
elif ymin is None:
|
elif ymin is None:
|
||||||
assert(yctr is not None)
|
assert yctr is not None
|
||||||
assert(ymax is not None)
|
assert ymax is not None
|
||||||
ly = 2 * (ymax - yctr)
|
ly = 2 * (ymax - yctr)
|
||||||
else:
|
else:
|
||||||
raise PatternError('Two of ymin, yctr, ymax, ly must be None!')
|
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:
|
if yctr is not None:
|
||||||
pass
|
pass
|
||||||
elif ymax is None:
|
elif ymax is None:
|
||||||
assert(ymin is not None)
|
assert ymin is not None
|
||||||
assert(ly is not None)
|
assert ly is not None
|
||||||
yctr = ymin + 0.5 * ly
|
yctr = ymin + 0.5 * ly
|
||||||
elif ymin is None:
|
elif ymin is None:
|
||||||
assert(ly is not None)
|
assert ly is not None
|
||||||
assert(ymax is not None)
|
assert ymax is not None
|
||||||
yctr = ymax - 0.5 * ly
|
yctr = ymax - 0.5 * ly
|
||||||
else:
|
else:
|
||||||
raise PatternError('Two of ymin, yctr, ymax, ly must be None!')
|
raise PatternError('Two of ymin, yctr, ymax, ly must be None!')
|
||||||
@ -331,7 +331,6 @@ class Polygon(Shape, metaclass=AutoSlots):
|
|||||||
poly.rotate(rotation)
|
poly.rotate(rotation)
|
||||||
return poly
|
return poly
|
||||||
|
|
||||||
|
|
||||||
def to_polygons(
|
def to_polygons(
|
||||||
self,
|
self,
|
||||||
poly_num_points: Optional[int] = None, # unused
|
poly_num_points: Optional[int] = None, # unused
|
||||||
|
@ -79,8 +79,8 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
|
|||||||
raw: bool = False,
|
raw: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
if raw:
|
if raw:
|
||||||
assert(isinstance(offset, numpy.ndarray))
|
assert isinstance(offset, numpy.ndarray)
|
||||||
assert(isinstance(mirrored, numpy.ndarray))
|
assert isinstance(mirrored, numpy.ndarray)
|
||||||
self._offset = offset
|
self._offset = offset
|
||||||
self._layer = layer
|
self._layer = layer
|
||||||
self._string = string
|
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()`
|
This is handy for destructuring like `xy_min, xy_max = entity.get_bounds_nonempty()`
|
||||||
"""
|
"""
|
||||||
bounds = self.get_bounds()
|
bounds = self.get_bounds()
|
||||||
assert(bounds is not None)
|
assert bounds is not None
|
||||||
return bounds
|
return bounds
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,11 +3,11 @@ from abc import ABCMeta, abstractmethod
|
|||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
from numpy.typing import ArrayLike, NDArray
|
from numpy.typing import ArrayLike
|
||||||
|
|
||||||
from .positionable import Positionable
|
from .positionable import Positionable
|
||||||
from ..error import MasqueError
|
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
|
_empty_slots = () # Workaround to get mypy to ignore intentionally empty slots for superclass
|
||||||
|
@ -11,31 +11,6 @@ from ..pattern import Pattern
|
|||||||
from ..ref import Ref
|
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(
|
def maxrects_bssf(
|
||||||
rects: ArrayLike,
|
rects: ArrayLike,
|
||||||
containers: ArrayLike,
|
containers: ArrayLike,
|
||||||
@ -165,3 +140,28 @@ def guillotine_bssf_sas(rect_sizes: numpy.ndarray,
|
|||||||
new_region0, new_region1))
|
new_region0, new_region1))
|
||||||
|
|
||||||
return rect_locs, rejected_inds
|
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
|
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]
|
layer_t = Union[int, Tuple[int, int], str]
|
||||||
|
@ -83,7 +83,7 @@ def poly_contains_points(
|
|||||||
max_bounds = numpy.max(vertices, axis=0)[None, :]
|
max_bounds = numpy.max(vertices, axis=0)[None, :]
|
||||||
|
|
||||||
trivially_outside = ((points < min_bounds).any(axis=1)
|
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
|
nontrivial = ~trivially_outside
|
||||||
if trivially_outside.all():
|
if trivially_outside.all():
|
||||||
@ -101,10 +101,10 @@ def poly_contains_points(
|
|||||||
|
|
||||||
dv = numpy.roll(verts, -1, axis=0) - verts
|
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
|
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)
|
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
|
nontrivial_inside = winding_number != 0 # filter nontrivial points based on winding number
|
||||||
if include_boundary:
|
if include_boundary:
|
||||||
|
Loading…
Reference in New Issue
Block a user