|
|
|
@ -18,8 +18,8 @@ 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, Sequence, Iterable, Optional
|
|
|
|
|
from typing import Sequence, Mapping, BinaryIO
|
|
|
|
|
from typing import List, Any, Dict, Tuple, Callable, Union, Iterable, Optional
|
|
|
|
|
from typing import Sequence, BinaryIO
|
|
|
|
|
import re
|
|
|
|
|
import io
|
|
|
|
|
import mmap
|
|
|
|
@ -29,18 +29,16 @@ import struct
|
|
|
|
|
import logging
|
|
|
|
|
import pathlib
|
|
|
|
|
import gzip
|
|
|
|
|
from itertools import chain
|
|
|
|
|
|
|
|
|
|
import numpy # type: ignore
|
|
|
|
|
import klamath
|
|
|
|
|
from klamath import records
|
|
|
|
|
|
|
|
|
|
from .utils import mangle_name, make_dose_table, dose2dtype, dtype2dose, is_gzipped
|
|
|
|
|
from .utils import is_gzipped
|
|
|
|
|
from .. import Pattern, SubPattern, PatternError, Label, Shape
|
|
|
|
|
from ..shapes import Polygon, Path
|
|
|
|
|
from ..repetition import Grid
|
|
|
|
|
from ..utils import rotation_matrix_2d, get_bit, set_bit, vector2, is_scalar, layer_t
|
|
|
|
|
from ..utils import remove_colinear_vertices, normalize_mirror, annotations_t
|
|
|
|
|
from ..utils import layer_t, normalize_mirror, annotations_t
|
|
|
|
|
from ..library import Library
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
@ -144,15 +142,15 @@ def writefile(patterns: Union[Sequence[Pattern], Pattern],
|
|
|
|
|
**kwargs,
|
|
|
|
|
) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Wrapper for `masque.file.gdsii.write()` that takes a filename or path instead of a stream.
|
|
|
|
|
Wrapper for `write()` that takes a filename or path instead of a stream.
|
|
|
|
|
|
|
|
|
|
Will automatically compress the file if it has a .gz suffix.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
patterns: `Pattern` or list of patterns to save
|
|
|
|
|
filename: Filename to save to.
|
|
|
|
|
*args: passed to `masque.file.gdsii.write`
|
|
|
|
|
**kwargs: passed to `masque.file.gdsii.write`
|
|
|
|
|
*args: passed to `write()`
|
|
|
|
|
**kwargs: passed to `write()`
|
|
|
|
|
"""
|
|
|
|
|
path = pathlib.Path(filename)
|
|
|
|
|
if path.suffix == '.gz':
|
|
|
|
@ -169,14 +167,14 @@ def readfile(filename: Union[str, pathlib.Path],
|
|
|
|
|
**kwargs,
|
|
|
|
|
) -> Tuple[Dict[str, Pattern], Dict[str, Any]]:
|
|
|
|
|
"""
|
|
|
|
|
Wrapper for `masque.file.gdsii.read()` that takes a filename or path instead of a stream.
|
|
|
|
|
Wrapper for `read()` that takes a filename or path instead of a stream.
|
|
|
|
|
|
|
|
|
|
Will automatically decompress gzipped files.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
filename: Filename to save to.
|
|
|
|
|
*args: passed to `masque.file.gdsii.read`
|
|
|
|
|
**kwargs: passed to `masque.file.gdsii.read`
|
|
|
|
|
*args: passed to `read()`
|
|
|
|
|
**kwargs: passed to `read()`
|
|
|
|
|
"""
|
|
|
|
|
path = pathlib.Path(filename)
|
|
|
|
|
if is_gzipped(path):
|
|
|
|
@ -185,7 +183,7 @@ def readfile(filename: Union[str, pathlib.Path],
|
|
|
|
|
open_func = open
|
|
|
|
|
|
|
|
|
|
with io.BufferedReader(open_func(path, mode='rb')) as stream:
|
|
|
|
|
results = read(stream)#, *args, **kwargs)
|
|
|
|
|
results = read(stream, *args, **kwargs)
|
|
|
|
|
return results
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -216,7 +214,7 @@ def read(stream: BinaryIO,
|
|
|
|
|
found_struct = records.BGNSTR.skip_past(stream)
|
|
|
|
|
while found_struct:
|
|
|
|
|
name = records.STRNAME.skip_and_read(stream)
|
|
|
|
|
pat = read_elements(stream, name=name.decode('ASCII'))
|
|
|
|
|
pat = read_elements(stream, name=name.decode('ASCII'), raw_mode=raw_mode)
|
|
|
|
|
patterns.append(pat)
|
|
|
|
|
found_struct = records.BGNSTR.skip_past(stream)
|
|
|
|
|
|
|
|
|
@ -412,7 +410,7 @@ def _annotations_to_properties(annotations: annotations_t, max_len: int = 126) -
|
|
|
|
|
for key, vals in annotations.items():
|
|
|
|
|
try:
|
|
|
|
|
i = int(key)
|
|
|
|
|
except:
|
|
|
|
|
except ValueError:
|
|
|
|
|
raise PatternError(f'Annotation key {key} is not convertable to an integer')
|
|
|
|
|
if not (0 < i < 126):
|
|
|
|
|
raise PatternError(f'Annotation key {key} converts to {i} (must be in the range [1,125])')
|
|
|
|
@ -496,7 +494,7 @@ def _labels_to_texts(labels: List[Label]) -> List[klamath.elements.Text]:
|
|
|
|
|
def disambiguate_pattern_names(patterns: Sequence[Pattern],
|
|
|
|
|
max_name_length: int = 32,
|
|
|
|
|
suffix_length: int = 6,
|
|
|
|
|
dup_warn_filter: Optional[Callable[[str,], bool]] = None,
|
|
|
|
|
dup_warn_filter: Optional[Callable[[str], bool]] = None,
|
|
|
|
|
):
|
|
|
|
|
"""
|
|
|
|
|
Args:
|
|
|
|
@ -513,13 +511,13 @@ def disambiguate_pattern_names(patterns: Sequence[Pattern],
|
|
|
|
|
# Shorten names which already exceed max-length
|
|
|
|
|
if len(pat.name) > max_name_length:
|
|
|
|
|
shortened_name = pat.name[:max_name_length - suffix_length]
|
|
|
|
|
logger.warning(f'Pattern name "{pat.name}" is too long ({len(pat.name)}/{max_name_length} chars),\n' +
|
|
|
|
|
f' shortening to "{shortened_name}" before generating suffix')
|
|
|
|
|
logger.warning(f'Pattern name "{pat.name}" is too long ({len(pat.name)}/{max_name_length} chars),\n'
|
|
|
|
|
+ f' shortening to "{shortened_name}" before generating suffix')
|
|
|
|
|
else:
|
|
|
|
|
shortened_name = pat.name
|
|
|
|
|
|
|
|
|
|
# Remove invalid characters
|
|
|
|
|
sanitized_name = re.compile('[^A-Za-z0-9_\?\$]').sub('_', shortened_name)
|
|
|
|
|
sanitized_name = re.compile(r'[^A-Za-z0-9_\?\$]').sub('_', shortened_name)
|
|
|
|
|
|
|
|
|
|
# Add a suffix that makes the name unique
|
|
|
|
|
i = 0
|
|
|
|
@ -534,8 +532,8 @@ def disambiguate_pattern_names(patterns: Sequence[Pattern],
|
|
|
|
|
logger.warning(f'Empty pattern name saved as "{suffixed_name}"')
|
|
|
|
|
elif suffixed_name != sanitized_name:
|
|
|
|
|
if dup_warn_filter is None or dup_warn_filter(pat.name):
|
|
|
|
|
logger.warning(f'Pattern name "{pat.name}" ({sanitized_name}) appears multiple times;\n' +
|
|
|
|
|
f' renaming to "{suffixed_name}"')
|
|
|
|
|
logger.warning(f'Pattern name "{pat.name}" ({sanitized_name}) appears multiple times;\n'
|
|
|
|
|
+ f' renaming to "{suffixed_name}"')
|
|
|
|
|
|
|
|
|
|
# Encode into a byte-string and perform some final checks
|
|
|
|
|
encoded_name = suffixed_name.encode('ASCII')
|
|
|
|
@ -543,8 +541,8 @@ def disambiguate_pattern_names(patterns: Sequence[Pattern],
|
|
|
|
|
# Should never happen since zero-length names are replaced
|
|
|
|
|
raise PatternError(f'Zero-length name after sanitize+encode,\n originally "{pat.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 "{pat.name}"')
|
|
|
|
|
raise PatternError(f'Pattern name "{encoded_name!r}" length > {max_name_length} after encode,\n'
|
|
|
|
|
+ f' originally "{pat.name}"')
|
|
|
|
|
|
|
|
|
|
pat.name = suffixed_name
|
|
|
|
|
used_names.append(suffixed_name)
|
|
|
|
@ -576,7 +574,8 @@ def load_library(stream: BinaryIO,
|
|
|
|
|
Additional library info (dict, same format as from `read`).
|
|
|
|
|
"""
|
|
|
|
|
if is_secondary is None:
|
|
|
|
|
is_secondary = lambda k: False
|
|
|
|
|
def is_secondary(k: str):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
stream.seek(0)
|
|
|
|
|
library_info = _read_header(stream)
|
|
|
|
@ -592,7 +591,7 @@ def load_library(stream: BinaryIO,
|
|
|
|
|
|
|
|
|
|
lib.set_value(name, tag, mkstruct, secondary=is_secondary(name))
|
|
|
|
|
|
|
|
|
|
return lib
|
|
|
|
|
return lib, library_info
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load_libraryfile(filename: Union[str, pathlib.Path],
|
|
|
|
|