diff --git a/examples/test_rep.py b/examples/test_rep.py index 042e1af..4c99aa5 100644 --- a/examples/test_rep.py +++ b/examples/test_rep.py @@ -1,103 +1,135 @@ +from pprint import pprint +from pathlib import Path + import numpy from numpy import pi import masque -import masque.file.gdsii -import masque.file.klamath -import masque.file.dxf -import masque.file.oasis -from masque import shapes, Pattern, SubPattern +from masque import Pattern, Ref, Arc, WrapLibrary from masque.repetition import Grid +from masque.file import gdsii, dxf, oasis -from pprint import pprint def main(): - pat = masque.Pattern(name='ellip_grating') + lib = WrapLibrary() + + cell_name = 'ellip_grating' + pat = masque.Pattern() for rmin in numpy.arange(10, 15, 0.5): - pat.shapes.append(shapes.Arc( + pat.shapes.append(Arc( radii=(rmin, rmin), width=0.1, - angles=(0*-numpy.pi/4, numpy.pi/4), + angles=(0 * -pi/4, pi/4), annotations={'1': ['blah']}, - )) + )) pat.scale_by(1000) # pat.visualize() - pat2 = pat.copy() - pat2.name = 'grating2' - - pat3 = Pattern('sref_test') - pat3.subpatterns = [ - SubPattern(pat, offset=(1e5, 3e5), annotations={'4': ['Hello I am the base subpattern']}), - SubPattern(pat, offset=(2e5, 3e5), rotation=pi/3), - SubPattern(pat, offset=(3e5, 3e5), rotation=pi/2), - SubPattern(pat, offset=(4e5, 3e5), rotation=pi), - SubPattern(pat, offset=(5e5, 3e5), rotation=3*pi/2), - SubPattern(pat, mirrored=(True, False), offset=(1e5, 4e5)), - SubPattern(pat, mirrored=(True, False), offset=(2e5, 4e5), rotation=pi/3), - SubPattern(pat, mirrored=(True, False), offset=(3e5, 4e5), rotation=pi/2), - SubPattern(pat, mirrored=(True, False), offset=(4e5, 4e5), rotation=pi), - SubPattern(pat, mirrored=(True, False), offset=(5e5, 4e5), rotation=3*pi/2), - SubPattern(pat, mirrored=(False, True), offset=(1e5, 5e5)), - SubPattern(pat, mirrored=(False, True), offset=(2e5, 5e5), rotation=pi/3), - SubPattern(pat, mirrored=(False, True), offset=(3e5, 5e5), rotation=pi/2), - SubPattern(pat, mirrored=(False, True), offset=(4e5, 5e5), rotation=pi), - SubPattern(pat, mirrored=(False, True), offset=(5e5, 5e5), rotation=3*pi/2), - SubPattern(pat, mirrored=(True, True), offset=(1e5, 6e5)), - SubPattern(pat, mirrored=(True, True), offset=(2e5, 6e5), rotation=pi/3), - SubPattern(pat, mirrored=(True, True), offset=(3e5, 6e5), rotation=pi/2), - SubPattern(pat, mirrored=(True, True), offset=(4e5, 6e5), rotation=pi), - SubPattern(pat, mirrored=(True, True), offset=(5e5, 6e5), rotation=3*pi/2), - ] - - pprint(pat3) - pprint(pat3.subpatterns) + lib[cell_name] = pat + print(f'\nAdded {cell_name}:') pprint(pat.shapes) - rep = Grid(a_vector=[1e4, 0], - b_vector=[0, 1.5e4], - a_count=3, - b_count=2,) - pat4 = Pattern('aref_test') - pat4.subpatterns = [ - SubPattern(pat, repetition=rep, offset=(1e5, 3e5)), - SubPattern(pat, repetition=rep, offset=(2e5, 3e5), rotation=pi/3), - SubPattern(pat, repetition=rep, offset=(3e5, 3e5), rotation=pi/2), - SubPattern(pat, repetition=rep, offset=(4e5, 3e5), rotation=pi), - SubPattern(pat, repetition=rep, offset=(5e5, 3e5), rotation=3*pi/2), - SubPattern(pat, repetition=rep, mirrored=(True, False), offset=(1e5, 4e5)), - SubPattern(pat, repetition=rep, mirrored=(True, False), offset=(2e5, 4e5), rotation=pi/3), - SubPattern(pat, repetition=rep, mirrored=(True, False), offset=(3e5, 4e5), rotation=pi/2), - SubPattern(pat, repetition=rep, mirrored=(True, False), offset=(4e5, 4e5), rotation=pi), - SubPattern(pat, repetition=rep, mirrored=(True, False), offset=(5e5, 4e5), rotation=3*pi/2), - SubPattern(pat, repetition=rep, mirrored=(False, True), offset=(1e5, 5e5)), - SubPattern(pat, repetition=rep, mirrored=(False, True), offset=(2e5, 5e5), rotation=pi/3), - SubPattern(pat, repetition=rep, mirrored=(False, True), offset=(3e5, 5e5), rotation=pi/2), - SubPattern(pat, repetition=rep, mirrored=(False, True), offset=(4e5, 5e5), rotation=pi), - SubPattern(pat, repetition=rep, mirrored=(False, True), offset=(5e5, 5e5), rotation=3*pi/2), - SubPattern(pat, repetition=rep, mirrored=(True, True), offset=(1e5, 6e5)), - SubPattern(pat, repetition=rep, mirrored=(True, True), offset=(2e5, 6e5), rotation=pi/3), - SubPattern(pat, repetition=rep, mirrored=(True, True), offset=(3e5, 6e5), rotation=pi/2), - SubPattern(pat, repetition=rep, mirrored=(True, True), offset=(4e5, 6e5), rotation=pi), - SubPattern(pat, repetition=rep, mirrored=(True, True), offset=(5e5, 6e5), rotation=3*pi/2), + new_name = lib.get_name(cell_name) + lib[new_name] = pat.copy() + print(f'\nAdded a copy of {cell_name} as {new_name}') + + pat3 = Pattern() + pat3.refs = [ + Ref(cell_name, offset=(1e5, 3e5), annotations={'4': ['Hello I am the base Ref']}), + Ref(cell_name, offset=(2e5, 3e5), rotation=pi/3), + Ref(cell_name, offset=(3e5, 3e5), rotation=pi/2), + Ref(cell_name, offset=(4e5, 3e5), rotation=pi), + Ref(cell_name, offset=(5e5, 3e5), rotation=3*pi/2), + Ref(cell_name, mirrored=(True, False), offset=(1e5, 4e5)), + Ref(cell_name, mirrored=(True, False), offset=(2e5, 4e5), rotation=pi/3), + Ref(cell_name, mirrored=(True, False), offset=(3e5, 4e5), rotation=pi/2), + Ref(cell_name, mirrored=(True, False), offset=(4e5, 4e5), rotation=pi), + Ref(cell_name, mirrored=(True, False), offset=(5e5, 4e5), rotation=3*pi/2), + Ref(cell_name, mirrored=(False, True), offset=(1e5, 5e5)), + Ref(cell_name, mirrored=(False, True), offset=(2e5, 5e5), rotation=pi/3), + Ref(cell_name, mirrored=(False, True), offset=(3e5, 5e5), rotation=pi/2), + Ref(cell_name, mirrored=(False, True), offset=(4e5, 5e5), rotation=pi), + Ref(cell_name, mirrored=(False, True), offset=(5e5, 5e5), rotation=3*pi/2), + Ref(cell_name, mirrored=(True, True), offset=(1e5, 6e5)), + Ref(cell_name, mirrored=(True, True), offset=(2e5, 6e5), rotation=pi/3), + Ref(cell_name, mirrored=(True, True), offset=(3e5, 6e5), rotation=pi/2), + Ref(cell_name, mirrored=(True, True), offset=(4e5, 6e5), rotation=pi), + Ref(cell_name, mirrored=(True, True), offset=(5e5, 6e5), rotation=3*pi/2), ] - folder = 'layouts/' - masque.file.klamath.writefile((pat, pat2, pat3, pat4), folder + 'rep.gds.gz', 1e-9, 1e-3) + lib['sref_test'] = pat3 + print('\nAdded sref_test:') + pprint(pat3) + pprint(pat3.refs) - cells = list(masque.file.klamath.readfile(folder + 'rep.gds.gz')[0].values()) - masque.file.klamath.writefile(cells, folder + 'rerep.gds.gz', 1e-9, 1e-3) + rep = Grid( + a_vector=[1e4, 0], + b_vector=[0, 1.5e4], + a_count=3, + b_count=2, + ) + pat4 = Pattern() + pat4.refs = [ + Ref(cell_name, repetition=rep, offset=(1e5, 3e5)), + Ref(cell_name, repetition=rep, offset=(2e5, 3e5), rotation=pi/3), + Ref(cell_name, repetition=rep, offset=(3e5, 3e5), rotation=pi/2), + Ref(cell_name, repetition=rep, offset=(4e5, 3e5), rotation=pi), + Ref(cell_name, repetition=rep, offset=(5e5, 3e5), rotation=3*pi/2), + Ref(cell_name, repetition=rep, mirrored=(True, False), offset=(1e5, 4e5)), + Ref(cell_name, repetition=rep, mirrored=(True, False), offset=(2e5, 4e5), rotation=pi/3), + Ref(cell_name, repetition=rep, mirrored=(True, False), offset=(3e5, 4e5), rotation=pi/2), + Ref(cell_name, repetition=rep, mirrored=(True, False), offset=(4e5, 4e5), rotation=pi), + Ref(cell_name, repetition=rep, mirrored=(True, False), offset=(5e5, 4e5), rotation=3*pi/2), + Ref(cell_name, repetition=rep, mirrored=(False, True), offset=(1e5, 5e5)), + Ref(cell_name, repetition=rep, mirrored=(False, True), offset=(2e5, 5e5), rotation=pi/3), + Ref(cell_name, repetition=rep, mirrored=(False, True), offset=(3e5, 5e5), rotation=pi/2), + Ref(cell_name, repetition=rep, mirrored=(False, True), offset=(4e5, 5e5), rotation=pi), + Ref(cell_name, repetition=rep, mirrored=(False, True), offset=(5e5, 5e5), rotation=3*pi/2), + Ref(cell_name, repetition=rep, mirrored=(True, True), offset=(1e5, 6e5)), + Ref(cell_name, repetition=rep, mirrored=(True, True), offset=(2e5, 6e5), rotation=pi/3), + Ref(cell_name, repetition=rep, mirrored=(True, True), offset=(3e5, 6e5), rotation=pi/2), + Ref(cell_name, repetition=rep, mirrored=(True, True), offset=(4e5, 6e5), rotation=pi), + Ref(cell_name, repetition=rep, mirrored=(True, True), offset=(5e5, 6e5), rotation=3*pi/2), + ] - masque.file.dxf.writefile(pat4, folder + 'rep.dxf.gz') - dxf, info = masque.file.dxf.readfile(folder + 'rep.dxf.gz') - masque.file.dxf.writefile(dxf, folder + 'rerep.dxf.gz') + lib['aref_test'] = pat4 + print('\nAdded aref_test') + + folder = Path('./layouts/') + print(f'...writing files to {folder}...') + + gds1 = folder / 'rep.gds.gz' + gds2 = folder / 'rerep.gds.gz' + print(f'Initial write to {gds1}') + gdsii.writefile(lib, gds1, 1e-9, 1e-3) + + print(f'Read back and rewrite to {gds2}') + readback_lib, _info = gdsii.readfile(gds1) + gdsii.writefile(readback_lib, gds2, 1e-9, 1e-3) + + dxf1 = folder / 'rep.dxf.gz' + dxf2 = folder / 'rerep.dxf.gz' + print(f'Write aref_test to {dxf1}') + dxf.writefile(lib, 'aref_test', dxf1) + + print(f'Read back and rewrite to {dxf2}') + dxf_lib, _info = dxf.readfile(dxf1) + print(WrapLibrary(dxf_lib)) + dxf.writefile(dxf_lib, 'Model', dxf2) layer_map = {'base': (0,0), 'mylabel': (1,2)} - masque.file.oasis.writefile((pat, pat2, pat3, pat4), folder + 'rep.oas.gz', 1000, layer_map=layer_map) - oas, info = masque.file.oasis.readfile(folder + 'rep.oas.gz') - masque.file.oasis.writefile(list(oas.values()), folder + 'rerep.oas.gz', 1000, layer_map=layer_map) - print(info) + oas1 = folder / 'rep.oas' + oas2 = folder / 'rerep.oas' + print(f'Write lib to {oas1}') + oasis.writefile(lib, oas1, 1000, layer_map=layer_map) + + print(f'Read back and rewrite to {oas2}') + oas_lib, oas_info = oasis.readfile(oas1) + oasis.writefile(oas_lib, oas2, 1000, layer_map=layer_map) + + print('OASIS info:') + pprint(oas_info) if __name__ == '__main__': diff --git a/masque/__init__.py b/masque/__init__.py index 3f74975..c2f0f6d 100644 --- a/masque/__init__.py +++ b/masque/__init__.py @@ -35,7 +35,9 @@ from .pattern import Pattern from .utils import layer_t, annotations_t from .library import Library, MutableLibrary, WrapROLibrary, WrapLibrary, LazyLibrary, AbstractView from .ports import Port, PortList -from .builder import Builder, Abstract, Tool +from .abstract import Abstract +from .builder import Builder, Tool + __author__ = 'Jan Petykiewicz' diff --git a/masque/builder/abstract.py b/masque/abstract.py similarity index 82% rename from masque/builder/abstract.py rename to masque/abstract.py index e788d7d..f4916c4 100644 --- a/masque/builder/abstract.py +++ b/masque/abstract.py @@ -3,13 +3,12 @@ from typing import MutableMapping, TYPE_CHECKING import copy import logging -from ..pattern import Pattern -from ..library import MutableLibrary -from ..ports import PortList, Port -from .tools import Tool +from .pattern import Pattern +from .ports import PortList, Port if TYPE_CHECKING: - from .builder import Builder + from .builder import Builder, Tool + from .library import MutableLibrary logger = logging.getLogger(__name__) @@ -34,8 +33,8 @@ class Abstract(PortList): def build( self, - library: MutableLibrary, - tools: Union[None, Tool, MutableMapping[Optional[str], Tool]] = None, + library: 'MutableLibrary', + tools: Union[None, 'Tool', MutableMapping[Optional[str], 'Tool']] = None, ) -> 'Builder': """ Begin building a new device around an instance of the current device diff --git a/masque/builder/__init__.py b/masque/builder/__init__.py index fc839cb..27cc0f9 100644 --- a/masque/builder/__init__.py +++ b/masque/builder/__init__.py @@ -1,4 +1,3 @@ from .builder import Builder -from .abstract import Abstract from .utils import ell from .tools import Tool diff --git a/masque/builder/builder.py b/masque/builder/builder.py index 77ccd70..9bf2c48 100644 --- a/masque/builder/builder.py +++ b/masque/builder/builder.py @@ -12,9 +12,9 @@ from ..ref import Ref from ..library import MutableLibrary from ..error import PortError, BuildError from ..ports import PortList, Port +from ..abstract import Abstract from .tools import Tool from .utils import ell -from .abstract import Abstract logger = logging.getLogger(__name__) diff --git a/masque/file/dxf.py b/masque/file/dxf.py index 86184b1..278caa5 100644 --- a/masque/file/dxf.py +++ b/masque/file/dxf.py @@ -14,27 +14,29 @@ import pathlib import gzip import numpy -import ezdxf # type: ignore +import ezdxf -from .. import Pattern, Ref, PatternError, Label, Shape -from ..shapes import Polygon, Path +from .. import Pattern, Ref, PatternError, Label +from ..library import Library, WrapROLibrary +from ..shapes import Shape, Polygon, Path from ..repetition import Grid from ..utils import rotation_matrix_2d, layer_t +from .utils import is_gzipped logger = logging.getLogger(__name__) -logger.warning('DXF support is experimental and only slightly tested!') +logger.warning('DXF support is experimental!') DEFAULT_LAYER = 'DEFAULT' def write( + library: Mapping[str, Pattern], # TODO could allow library=None for flat DXF top_name: str, - library: Mapping[str, Pattern], - stream: io.TextIOBase, + stream: TextIO, *, dxf_version='AC1024', ) -> None: @@ -65,17 +67,23 @@ def write( array with rotated instances must be manhattan _after_ having a compensating rotation applied. Args: - top_name: Name of the top-level pattern to write. library: A {name: Pattern} mapping of patterns. Only `top_name` and patterns referenced by it are written. + top_name: Name of the top-level pattern to write. stream: Stream object to write to. disambiguate_func: Function which takes a list of patterns and alters them to make their names valid and unique. Default is `disambiguate_pattern_names`. WARNING: No additional error checking is performed on the results. """ #TODO consider supporting DXF arcs? + if not isinstance(library, Library): + if isinstance(library, dict): + library = WrapROLibrary(library) + else: + library = WrapROLibrary(dict(library)) pattern = library[top_name] + subtree = library.subtree(top_name) # Create library lib = ezdxf.new(dxf_version, setup=True) @@ -85,8 +93,11 @@ def write( _mrefs_to_drefs(msp, pattern.refs) # Now create a block for each referenced pattern, and add in any shapes - for name, pat in library.items(): + for name, pat in subtree.items(): assert pat is not None + if name == top_name: + continue + block = lib.blocks.new(name=name) _shapes_to_elements(block, pat.shapes) @@ -97,8 +108,8 @@ def write( def writefile( - top_name: str, library: Mapping[str, Pattern], + top_name: str, filename: Union[str, pathlib.Path], *args, **kwargs, @@ -109,9 +120,9 @@ def writefile( Will automatically compress the file if it has a .gz suffix. Args: - top_name: Name of the top-level pattern to write. library: A {name: Pattern} mapping of patterns. Only `top_name` and patterns referenced by it are written. + top_name: Name of the top-level pattern to write. filename: Filename to save to. *args: passed to `dxf.write` **kwargs: passed to `dxf.write` @@ -181,19 +192,19 @@ def read( lib = ezdxf.read(stream) msp = lib.modelspace() - npat = _read_block(msp, clean_vertices) + npat = _read_block(msp) patterns_dict = dict( - [npat] + [_read_block(bb, clean_vertices) for bb in lib.blocks if bb.name != '*Model_Space'] + [npat] + [_read_block(bb) for bb in lib.blocks if bb.name != '*Model_Space'] ) - library_info = { - 'layers': [ll.dxfattribs() for ll in lib.layers] - } + library_info = dict( + layers=[ll.dxfattribs() for ll in lib.layers], + ) return patterns_dict, library_info -def _read_block(block, clean_vertices: bool) -> Tuple[str, Pattern]: +def _read_block(block) -> Tuple[str, Pattern]: name = block.name pat = Pattern() for element in block: @@ -225,18 +236,13 @@ def _read_block(block, clean_vertices: bool) -> Tuple[str, Pattern]: else: shape = Path(layer=layer, width=width, vertices=points[:, :2]) - if clean_vertices: - try: - shape.clean_vertices() - except PatternError: - continue - pat.shapes.append(shape) elif eltype in ('TEXT',): - args = {'offset': numpy.array(element.get_pos()[1])[:2], - 'layer': element.dxfattribs().get('layer', DEFAULT_LAYER), - } + args = dict( + offset=numpy.array(element.get_pos()[1])[:2], + layer=element.dxfattribs().get('layer', DEFAULT_LAYER), + ) string = element.dxfattribs().get('text', '') # height = element.dxfattribs().get('height', 0) # if height != 0: @@ -257,20 +263,21 @@ def _read_block(block, clean_vertices: bool) -> Tuple[str, Pattern]: offset = numpy.array(attr.get('insert', (0, 0, 0)))[:2] - args = { - 'target': (attr.get('name', None),), - 'offset': offset, - 'scale': scale, - 'mirrored': mirrored, - 'rotation': rotation, - 'pattern': None, - } + args = dict( + target=attr.get('name', None), + offset=offset, + scale=scale, + mirrored=mirrored, + rotation=rotation, + ) if 'column_count' in attr: - args['repetition'] = Grid(a_vector=(attr['column_spacing'], 0), - b_vector=(0, attr['row_spacing']), - a_count=attr['column_count'], - b_count=attr['row_count']) + args['repetition'] = Grid( + a_vector=(attr['column_spacing'], 0), + b_vector=(0, attr['row_spacing']), + a_count=attr['column_count'], + b_count=attr['row_count'], + ) pat.ref(**args) else: logger.warning(f'Ignoring DXF element {element.dxftype()} (not implemented).') @@ -286,12 +293,12 @@ def _mrefs_to_drefs( continue encoded_name = ref.target - rotation = (ref.rotation * 180 / numpy.pi) % 360 - attribs = { - 'xscale': ref.scale * (-1 if ref.mirrored[1] else 1), - 'yscale': ref.scale * (-1 if ref.mirrored[0] else 1), - 'rotation': rotation, - } + rotation = numpy.rad2deg(ref.rotation) % 360 + attribs = dict( + xscale=ref.scale * (-1 if ref.mirrored[1] else 1), + yscale=ref.scale * (-1 if ref.mirrored[0] else 1), + rotation=rotation, + ) rep = ref.repetition if rep is None: @@ -338,7 +345,7 @@ def _shapes_to_elements( ' Please call library.wrap_repeated_shapes() before writing to file.' ) - attribs = {'layer': _mlayer2dxf(shape.layer)} + attribs = dict(layer=_mlayer2dxf(shape.layer)) for polygon in shape.to_polygons(): xy_open = polygon.vertices + polygon.offset xy_closed = numpy.vstack((xy_open, xy_open[0, :])) @@ -350,7 +357,7 @@ def _labels_to_texts( labels: List[Label], ) -> None: for label in labels: - attribs = {'layer': _mlayer2dxf(label.layer)} + attribs = dict(layer=_mlayer2dxf(label.layer)) xy = label.offset block.add_text(label.string, dxfattribs=attribs).set_pos(xy, align='BOTTOM_LEFT') diff --git a/masque/file/gdsii.py b/masque/file/gdsii.py index dbf9086..9450498 100644 --- a/masque/file/gdsii.py +++ b/masque/file/gdsii.py @@ -30,7 +30,7 @@ import string from pprint import pformat import numpy -from numpy.typing import NDArray, ArrayLike +from numpy.typing import ArrayLike, NDArray import klamath from klamath import records @@ -54,7 +54,7 @@ path_cap_map = { def rint_cast(val: ArrayLike) -> NDArray[numpy.int32]: - return numpy.rint(val, dtype=numpy.int32, casting='unsafe') + return numpy.rint(val).astype(numpy.int32) def write( diff --git a/masque/file/oasis.py b/masque/file/oasis.py index 11e7b90..522b5a8 100644 --- a/masque/file/oasis.py +++ b/masque/file/oasis.py @@ -39,7 +39,7 @@ from ..utils import layer_t, normalize_mirror, annotations_t logger = logging.getLogger(__name__) -logger.warning('OASIS support is experimental and mostly untested!') +logger.warning('OASIS support is experimental!') path_cap_map = { @@ -51,7 +51,7 @@ path_cap_map = { #TODO implement more shape types? def rint_cast(val: ArrayLike) -> NDArray[numpy.int64]: - return numpy.rint(val, dtype=numpy.int64, casting='unsafe') + return numpy.rint(val).astype(numpy.int64) def build( diff --git a/masque/library.py b/masque/library.py index 2886204..06f0673 100644 --- a/masque/library.py +++ b/masque/library.py @@ -22,7 +22,7 @@ from .error import LibraryError, PatternError from .utils import rotation_matrix_2d, normalize_mirror from .shapes import Shape, Polygon from .label import Label -from .builder.abstract import Abstract +from .abstract import Abstract if TYPE_CHECKING: from .pattern import Pattern @@ -37,7 +37,7 @@ ML = TypeVar('ML', bound='MutableLibrary') LL = TypeVar('LL', bound='LazyLibrary') -class Library(Mapping[str, Pattern], metaclass=ABCMeta): +class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta): # inherited abstract functions #def __getitem__(self, key: str) -> 'Pattern': #def __iter__(self) -> Iterator[str]: @@ -61,7 +61,7 @@ class Library(Mapping[str, Pattern], metaclass=ABCMeta): return Abstract(name=name, ports=self[name].ports) def __repr__(self) -> str: - return '' + return '' def dangling_references( self, @@ -142,7 +142,11 @@ class Library(Mapping[str, Pattern], metaclass=ABCMeta): Returns: A `WrapROLibrary` containing only `tops` and the patterns they reference. """ + if isinstance(tops, str): + tops = (tops,) + keep: Set[str] = self.referenced_patterns(tops) - set((None,)) # type: ignore + keep |= set(tops) filtered = {kk: vv for kk, vv in self.items() if kk in keep} new = WrapROLibrary(filtered) @@ -405,7 +409,7 @@ class Library(Mapping[str, Pattern], metaclass=ABCMeta): VVV = TypeVar('VVV') -class MutableLibrary(Generic[VVV], Library, metaclass=ABCMeta): +class MutableLibrary(Library, Generic[VVV], metaclass=ABCMeta): # inherited abstract functions #def __getitem__(self, key: str) -> 'Pattern': #def __iter__(self) -> Iterator[str]: @@ -627,7 +631,11 @@ class MutableLibrary(Generic[VVV], Library, metaclass=ABCMeta): Returns: A `Library` containing only `tops` and the patterns they reference. """ + if isinstance(tops, str): + tops = (tops,) + keep: Set[str] = self.referenced_patterns(tops) - set((None,)) # type: ignore + keep |= set(tops) new = type(self)() for key in keep: @@ -654,7 +662,7 @@ class WrapROLibrary(Library): return len(self.mapping) def __repr__(self) -> str: - return f'' + return f'' class WrapLibrary(MutableLibrary): @@ -662,9 +670,12 @@ class WrapLibrary(MutableLibrary): def __init__( self, - mapping: MutableMapping[str, 'Pattern'], + mapping: Optional[MutableMapping[str, 'Pattern']] = None, ) -> None: - self.mapping = mapping + if mapping is None: + self.mapping = {} + else: + self.mapping = mapping def __getitem__(self, key: str) -> 'Pattern': return self.mapping[key] @@ -688,7 +699,7 @@ class WrapLibrary(MutableLibrary): self[key] = other[key] def __repr__(self) -> str: - return f'' + return f'' class LazyLibrary(MutableLibrary): @@ -745,7 +756,7 @@ class LazyLibrary(MutableLibrary): self._set(key, other[key]) def __repr__(self) -> str: - return '' + return '' def precache(self: LL) -> LL: """ diff --git a/masque/pattern.py b/masque/pattern.py index ff4a6c2..6e29586 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -88,7 +88,9 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots): self.refs = list(refs) if ports is not None: - ports = dict(copy.deepcopy(ports)) + self.ports = dict(copy.deepcopy(ports)) + else: + self.ports = {} self.annotations = annotations if annotations is not None else {} diff --git a/masque/ref.py b/masque/ref.py index e9066d4..bcc8412 100644 --- a/masque/ref.py +++ b/masque/ref.py @@ -178,8 +178,8 @@ class Ref( def get_bounds( self, *, - pattern: Optional[Pattern] = None, - library: Optional[Mapping[str, Pattern]] = None, + pattern: Optional['Pattern'] = None, + library: Optional[Mapping[str, 'Pattern']] = None, ) -> Optional[NDArray[numpy.float64]]: """ Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the