wip again

This commit is contained in:
jan 2023-01-13 20:33:14 -08:00
commit 7ca017d993
26 changed files with 273 additions and 336 deletions

View file

@ -35,7 +35,6 @@ def write(
*,
modify_originals: bool = False,
dxf_version='AC1024',
disambiguate_func: Callable[[Iterable[str]], List[str]] = None,
) -> None:
"""
Write a `Pattern` to a DXF file, by first calling `.polygonize()` to change the shapes
@ -73,20 +72,15 @@ def write(
WARNING: No additional error checking is performed on the results.
"""
#TODO consider supporting DXF arcs?
if disambiguate_func is None:
disambiguate_func = lambda pats: disambiguate_pattern_names(pats)
assert(disambiguate_func is not None)
#TODO name checking
bad_keys = check_valid_names(library.keys())
if not modify_originals:
library = library.deepcopy()
pattern = library[top_name]
old_names = list(library.keys())
new_names = disambiguate_func(old_names)
renamed_lib = {new_name: library[old_name]
for old_name, new_name in zip(old_names, new_names)}
# Create library
lib = ezdxf.new(dxf_version, setup=True)
msp = lib.modelspace()
@ -95,7 +89,7 @@ def write(
_subpatterns_to_refs(msp, pattern.subpatterns)
# Now create a block for each referenced pattern, and add in any shapes
for name, pat in renamed_lib.items():
for name, pat in library.items():
assert(pat is not None)
block = lib.blocks.new(name=name)
@ -388,10 +382,6 @@ def disambiguate_pattern_names(
if sanitized_name == '':
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(name):
logger.warning(f'Pattern name "{name}" ({sanitized_name}) appears multiple times;\n'
+ f' renaming to "{suffixed_name}"')
if len(suffixed_name) == 0:
# Should never happen since zero-length names are replaced

View file

@ -19,7 +19,7 @@ Notes:
* 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
from typing import Sequence, BinaryIO, Mapping
import re
import io
import mmap
@ -40,7 +40,8 @@ from .. import Pattern, SubPattern, PatternError, Label, Shape
from ..shapes import Polygon, Path
from ..repetition import Grid
from ..utils import layer_t, normalize_mirror, annotations_t
from ..library import Library
from ..library import LazyLibrary, WrapLibrary, MutableLibrary
logger = logging.getLogger(__name__)
@ -65,7 +66,6 @@ def write(
library_name: str = 'masque-klamath',
*,
modify_originals: bool = False,
disambiguate_func: Callable[[Iterable[str]], List[str]] = None,
) -> None:
"""
Convert a library to a GDSII stream, mapping data as follows:
@ -100,24 +100,22 @@ def write(
modify_originals: If `True`, the original pattern is modified as part of the writing
process. Otherwise, a copy is made.
Default `False`.
disambiguate_func: Function which takes a list of pattern names and returns a list of names
altered to be valid and unique. Default is `disambiguate_pattern_names`, which
attempts to adhere to the GDSII standard reasonably well.
WARNING: No additional error checking is performed on the results.
"""
if disambiguate_func is None:
disambiguate_func = disambiguate_pattern_names
# TODO check name errors
bad_keys = check_valid_names(library.keys())
# TODO check all hierarchy present
if not modify_originals:
library = copy.deepcopy(library)
library = library.deepcopy() #TODO figure out best approach e.g. if lazy
for p in library.values():
library.add(p.wrap_repeated_shapes())
if not isinstance(library, MutableLibrary):
if isinstance(library, dict):
library = WrapLibrary(library)
else:
library = WrapLibrary(dict(library))
old_names = list(library.keys())
new_names = disambiguate_func(old_names)
renamed_lib = {new_name: library[old_name]
for old_name, new_name in zip(old_names, new_names)}
library.wrap_repeated_shapes()
# Create library
header = klamath.library.FileHeader(
@ -128,7 +126,7 @@ def write(
header.write(stream)
# Now create a structure for each pattern, and add in any Boundary and SREF elements
for name, pat in renamed_lib.items():
for name, pat in library.items():
elements: List[klamath.elements.Element] = []
elements += _shapes_to_elements(pat.shapes)
elements += _labels_to_texts(pat.labels)
@ -162,7 +160,7 @@ def writefile(
open_func = open
with io.BufferedWriter(open_func(path, mode='wb')) as stream:
write(patterns, stream, *args, **kwargs)
write(library, stream, *args, **kwargs)
def readfile(
@ -310,7 +308,7 @@ def _ref_to_subpat(ref: klamath.library.Reference) -> SubPattern:
a_count=a_count, b_count=b_count)
subpat = SubPattern(
pattern=ref.struct_name.decode('ASCII'),
target=ref.struct_name.decode('ASCII'),
offset=offset,
rotation=numpy.deg2rad(ref.angle_deg),
scale=ref.mag,
@ -547,10 +545,6 @@ def disambiguate_pattern_names(
if sanitized_name == '':
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(name):
logger.warning(f'Pattern name "{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')
@ -569,7 +563,7 @@ def load_library(
stream: BinaryIO,
*,
full_load: bool = False,
) -> Tuple[Library, Dict[str, Any]]:
) -> Tuple[LazyLibrary, Dict[str, Any]]:
"""
Scan a GDSII stream to determine what structures are present, and create
a library from them. This enables deferred reading of structures
@ -586,11 +580,11 @@ def load_library(
will be faster than using the resulting library's `precache` method.
Returns:
Library object, allowing for deferred load of structures.
LazyLibrary object, allowing for deferred load of structures.
Additional library info (dict, same format as from `read`).
"""
stream.seek(0)
lib = Library()
lib = LazyLibrary()
if full_load:
# Full load approach (immediately load everything)
@ -620,7 +614,7 @@ def load_libraryfile(
*,
use_mmap: bool = True,
full_load: bool = False,
) -> Tuple[Library, Dict[str, Any]]:
) -> Tuple[LazyLibrary, Dict[str, Any]]:
"""
Wrapper for `load_library()` that takes a filename or path instead of a stream.
@ -638,7 +632,7 @@ def load_libraryfile(
full_load: If `True`, immediately loads all data. See `load_library`.
Returns:
Library object, allowing for deferred load of structures.
LazyLibrary object, allowing for deferred load of structures.
Additional library info (dict, same format as from `read`).
"""
path = pathlib.Path(filename)

View file

@ -29,6 +29,7 @@ from fatamorgana.basic import PathExtensionScheme, AString, NString, PropStringR
from .utils import is_gzipped
from .. import Pattern, SubPattern, PatternError, Label, Shape
from ..library import WrapLibrary, MutableLibrary
from ..shapes import Polygon, Path, Circle
from ..repetition import Grid, Arbitrary, Repetition
from ..utils import layer_t, normalize_mirror, annotations_t
@ -57,7 +58,6 @@ def build(
units_per_micron: int,
layer_map: Optional[Dict[str, Union[int, Tuple[int, int]]]] = None,
*,
disambiguate_func: Optional[Callable[[Iterable[str]], List[str]]] = None,
annotations: Optional[annotations_t] = None,
) -> fatamorgana.OasisLayout:
"""
@ -90,15 +90,22 @@ def build(
into numbers, omit this argument, and manually generate the required
`fatamorgana.records.LayerName` entries.
Default is an empty dict (no names provided).
disambiguate_func: Function which takes a list of pattern names and returns a list of names
altered to be valid and unique. Default is `disambiguate_pattern_names`.
annotations: dictionary of key-value pairs which are saved as library-level properties
Returns:
`fatamorgana.OasisLayout`
"""
if isinstance(patterns, Pattern):
patterns = [patterns]
# TODO check names
bad_keys = check_valid_names(library.keys())
# TODO check all hierarchy present
if not isinstance(library, MutableLibrary):
if isinstance(library, dict):
library = WrapLibrary(library)
else:
library = WrapLibrary(dict(library))
if layer_map is None:
layer_map = {}
@ -132,13 +139,8 @@ def build(
else:
layer2oas = _mlayer2oas
old_names = list(library.keys())
new_names = disambiguate_func(old_names)
renamed_lib = {new_name: library[old_name]
for old_name, new_name in zip(old_names, new_names)}
# Now create a structure for each pattern
for name, pat in renamed_lib.items():
for name, pat in library.items():
structure = fatamorgana.Cell(name=name)
lib.cells.append(structure)
@ -152,7 +154,7 @@ def build(
def write(
patterns: Union[Sequence[Pattern], Pattern],
library: Mapping[str, Pattern], # NOTE: Pattern here should be treated as immutable!
stream: io.BufferedIOBase,
*args,
**kwargs,
@ -162,17 +164,17 @@ def write(
for details.
Args:
patterns: A Pattern or list of patterns to write to file.
library: A {name: Pattern} mapping of patterns to write.
stream: Stream to write to.
*args: passed to `oasis.build()`
**kwargs: passed to `oasis.build()`
"""
lib = build(patterns, *args, **kwargs)
lib = build(library, *args, **kwargs)
lib.write(stream)
def writefile(
patterns: Union[Sequence[Pattern], Pattern],
library: Mapping[str, Pattern], # NOTE: Pattern here should be treated as immutable!
filename: Union[str, pathlib.Path],
*args,
**kwargs,
@ -183,7 +185,7 @@ def writefile(
Will automatically compress the file if it has a .gz suffix.
Args:
patterns: `Pattern` or list of patterns to save
library: A {name: Pattern} mapping of patterns to write.
filename: Filename to save to.
*args: passed to `oasis.write`
**kwargs: passed to `oasis.write`
@ -195,7 +197,7 @@ def writefile(
open_func = open
with io.BufferedWriter(open_func(path, mode='wb')) as stream:
write(patterns, stream, *args, **kwargs)
write(library, stream, *args, **kwargs)
def readfile(
@ -281,11 +283,13 @@ def read(
vertices = numpy.cumsum(numpy.vstack(((0, 0), element.get_point_list()[:-1])), axis=0)
annotations = properties_to_annotations(element.properties, lib.propnames, lib.propstrings)
poly = Polygon(vertices=vertices,
layer=element.get_layer_tuple(),
offset=element.get_xy(),
annotations=annotations,
repetition=repetition)
poly = Polygon(
vertices=vertices,
layer=element.get_layer_tuple(),
offset=element.get_xy(),
annotations=annotations,
repetition=repetition,
)
pat.shapes.append(poly)
@ -304,14 +308,16 @@ def read(
element.get_extension_end()[1]))
annotations = properties_to_annotations(element.properties, lib.propnames, lib.propstrings)
path = Path(vertices=vertices,
layer=element.get_layer_tuple(),
offset=element.get_xy(),
repetition=repetition,
annotations=annotations,
width=element.get_half_width() * 2,
cap=cap,
**path_args)
path = Path(
vertices=vertices,
layer=element.get_layer_tuple(),
offset=element.get_xy(),
repetition=repetition,
annotations=annotations,
width=element.get_half_width() * 2,
cap=cap,
**path_args,
)
pat.shapes.append(path)
@ -319,12 +325,13 @@ def read(
width = element.get_width()
height = element.get_height()
annotations = properties_to_annotations(element.properties, lib.propnames, lib.propstrings)
rect = Polygon(layer=element.get_layer_tuple(),
offset=element.get_xy(),
repetition=repetition,
vertices=numpy.array(((0, 0), (1, 0), (1, 1), (0, 1))) * (width, height),
annotations=annotations,
)
rect = Polygon(
layer=element.get_layer_tuple(),
offset=element.get_xy(),
repetition=repetition,
vertices=numpy.array(((0, 0), (1, 0), (1, 1), (0, 1))) * (width, height),
annotations=annotations,
)
pat.shapes.append(rect)
elif isinstance(element, fatrec.Trapezoid):
@ -408,21 +415,24 @@ def read(
vertices[0, 1] += width
annotations = properties_to_annotations(element.properties, lib.propnames, lib.propstrings)
ctrapz = Polygon(layer=element.get_layer_tuple(),
offset=element.get_xy(),
repetition=repetition,
vertices=vertices,
annotations=annotations,
)
ctrapz = Polygon(
layer=element.get_layer_tuple(),
offset=element.get_xy(),
repetition=repetition,
vertices=vertices,
annotations=annotations,
)
pat.shapes.append(ctrapz)
elif isinstance(element, fatrec.Circle):
annotations = properties_to_annotations(element.properties, lib.propnames, lib.propstrings)
circle = Circle(layer=element.get_layer_tuple(),
offset=element.get_xy(),
repetition=repetition,
annotations=annotations,
radius=float(element.get_radius()))
circle = Circle(
layer=element.get_layer_tuple(),
offset=element.get_xy(),
repetition=repetition,
annotations=annotations,
radius=float(element.get_radius()),
)
pat.shapes.append(circle)
elif isinstance(element, fatrec.Text):
@ -432,11 +442,13 @@ def read(
string = lib.textstrings[str_or_ref].string
else:
string = str_or_ref.string
label = Label(layer=element.get_layer_tuple(),
offset=element.get_xy(),
repetition=repetition,
annotations=annotations,
string=string)
label = Label(
layer=element.get_layer_tuple(),
offset=element.get_xy(),
repetition=repetition,
annotations=annotations,
string=string,
)
pat.labels.append(label)
else:
@ -446,7 +458,7 @@ def read(
for placement in cell.placements:
pat.subpatterns.append(_placement_to_subpat(placement, lib))
patterns_dict[name] = pat
patterns_dict[cell_name] = pat
return patterns_dict, library_info
@ -516,7 +528,8 @@ def _subpatterns_to_placements(
properties=annotations_to_properties(subpat.annotations),
x=offset[0],
y=offset[1],
repetition=frep)
repetition=frep,
)
refs.append(ref)
return refs
@ -605,7 +618,6 @@ def _labels_to_texts(
def disambiguate_pattern_names(
names: Iterable[str],
dup_warn_filter: Callable[[str], bool] = None, # If returns False, don't warn about this name
) -> List[str]:
new_names = []
for name in names:
@ -621,10 +633,6 @@ def disambiguate_pattern_names(
if sanitized_name == '':
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(name):
logger.warning(f'Pattern name "{name}" ({sanitized_name}) appears multiple times;\n'
+ f' renaming to "{suffixed_name}"')
if len(suffixed_name) == 0:
# Should never happen since zero-length names are replaced

View file

@ -65,7 +65,6 @@ def build(
library_name: str = 'masque-gdsii-write',
*,
modify_originals: bool = False,
disambiguate_func: Callable[[Iterable[str]], List[str]] = None,
) -> gdsii.library.Library:
"""
Convert a `Pattern` or list of patterns to a GDSII stream, by first calling
@ -97,22 +96,20 @@ def build(
modify_originals: If `True`, the original pattern is modified as part of the writing
process. Otherwise, a copy is made.
Default `False`.
disambiguate_func: Function which takes a list of pattern names and returns a list of names
altered to be valid and unique. Default is `disambiguate_pattern_names`, which
attempts to adhere to the GDSII standard reasonably well.
WARNING: No additional error checking is performed on the results.
Returns:
`gdsii.library.Library`
"""
if disambiguate_func is None:
disambiguate_func = disambiguate_pattern_names
# TODO check name errors
bad_keys = check_valid_names(library.keys())
# TODO check all hierarchy present
if not modify_originals:
library = copy.deepcopy(library)
library = library.deepcopy() #TODO figure out best approach e.g. if lazy
for p in library.values():
library.add(p.wrap_repeated_shapes())
library.wrap_repeated_shapes()
old_names = list(library.keys())
new_names = disambiguate_func(old_names)
@ -181,7 +178,7 @@ def writefile(
open_func = open
with io.BufferedWriter(open_func(path, mode='wb')) as stream:
write(patterns, stream, *args, **kwargs)
write(library, stream, *args, **kwargs)
def readfile(
@ -248,7 +245,7 @@ def read(
patterns_dict = {}
for structure in lib:
pat = Pattern()
name=structure.name.decode('ASCII')
name = structure.name.decode('ASCII')
for element in structure:
# Switch based on element type:
if isinstance(element, gdsii.elements.Boundary):
@ -260,9 +257,11 @@ def read(
pat.shapes.append(path)
elif isinstance(element, gdsii.elements.Text):
label = Label(offset=element.xy.astype(float),
layer=(element.layer, element.text_type),
string=element.string.decode('ASCII'))
label = Label(
offset=element.xy.astype(float),
layer=(element.layer, element.text_type),
string=element.string.decode('ASCII'),
)
pat.labels.append(label)
elif isinstance(element, (gdsii.elements.SRef, gdsii.elements.ARef)):
@ -296,7 +295,7 @@ def _ref_to_subpat(
gdsii.elements.ARef]
) -> SubPattern:
"""
Helper function to create a SubPattern from an SREF or AREF. Sets subpat.target to struct_name.
Helper function to create a SubPattern from an SREF or AREF. Sets `subpat.target` to `element.struct_name`.
NOTE: "Absolute" means not affected by parent elements.
That's not currently supported by masque at all (and not planned).
@ -330,13 +329,15 @@ def _ref_to_subpat(
repetition = Grid(a_vector=a_vector, b_vector=b_vector,
a_count=a_count, b_count=b_count)
subpat = SubPattern(pattern=None,
offset=offset,
rotation=rotation,
scale=scale,
mirrored=(mirror_across_x, False),
annotations=_properties_to_annotations(element.properties),
repetition=repetition)
subpat = SubPattern(
target=element.struct_name,
offset=offset,
rotation=rotation,
scale=scale,
mirrored=(mirror_across_x, False),
annotations=_properties_to_annotations(element.properties),
repetition=repetition,
)
return subpat
@ -346,14 +347,15 @@ def _gpath_to_mpath(element: gdsii.elements.Path, raw_mode: bool) -> Path:
else:
raise PatternError(f'Unrecognized path type: {element.path_type}')
args = {'vertices': element.xy.astype(float),
'layer': (element.layer, element.data_type),
'width': element.width if element.width is not None else 0.0,
'cap': cap,
'offset': numpy.zeros(2),
'annotations': _properties_to_annotations(element.properties),
'raw': raw_mode,
}
args = {
'vertices': element.xy.astype(float),
'layer': (element.layer, element.data_type),
'width': element.width if element.width is not None else 0.0,
'cap': cap,
'offset': numpy.zeros(2),
'annotations': _properties_to_annotations(element.properties),
'raw': raw_mode,
}
if cap == Path.Cap.SquareCustom:
args['cap_extensions'] = numpy.zeros(2)
@ -511,7 +513,6 @@ def disambiguate_pattern_names(
names: Iterable[str],
max_name_length: int = 32,
suffix_length: int = 6,
dup_warn_filter: Optional[Callable[[str], bool]] = None,
) -> List[str]:
"""
Args:
@ -519,9 +520,6 @@ def disambiguate_pattern_names(
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.
dup_warn_filter: (optional) Function for suppressing warnings about cell names changing. Receives
the cell name and returns `False` if the warning should be suppressed and `True` if it should
be displayed. Default displays all warnings.
"""
new_names = []
for name in names:
@ -547,10 +545,6 @@ def disambiguate_pattern_names(
if sanitized_name == '':
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(name):
logger.warning(f'Pattern name "{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')

View file

@ -114,7 +114,7 @@ def writefile_inverted(
pattern = library[top]
# Polygonize and flatten pattern
pattern.polygonize().flatten()
pattern.polygonize().flatten(library)
bounds = pattern.get_bounds(library=library)
if bounds is None:

View file

@ -5,11 +5,16 @@ 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
logger = logging.getLogger(__name__)
def mangle_name(name: str, dose_multiplier: float = 1.0) -> str:
"""
Create a new name using `name` and the `dose_multiplier`.
@ -58,7 +63,7 @@ def make_dose_table(
top_names: Iterable[str],
library: Mapping[str, Pattern],
dose_multiplier: float = 1.0,
) -> Set[Tuple[int, float]]:
) -> Set[Tuple[str, float]]:
"""
Create a set containing `(name, written_dose)` for each pattern (including subpatterns)
@ -104,7 +109,7 @@ def dtype2dose(pattern: Pattern) -> Pattern:
def dose2dtype(
library: List[Pattern],
library: Mapping[str, Pattern],
) -> Tuple[List[Pattern], List[float]]:
"""
For each shape in each pattern, set shape.layer to the tuple
@ -128,6 +133,10 @@ def dose2dtype(
and dose (float, list entry).
"""
logger.warning('TODO: dose2dtype() needs to be tested!')
if not isinstance(library, Library):
library = WrapROLibrary(library)
# Get a table of (id(pat), written_dose) for each pattern and subpattern
sd_table = make_dose_table(library.find_topcells(), library)
@ -161,8 +170,8 @@ def dose2dtype(
pat = old_pat.deepcopy()
if len(encoded_name) == 0:
raise PatternError('Zero-length name after mangle+encode, originally "{name}"'.format(pat.name))
if len(mangled_name) == 0:
raise PatternError(f'Zero-length name after mangle, originally "{name}"')
for shape in pat.shapes:
data_type = dose_vals_list.index(shape.dose * pat_dose)