masque/masque/file/gdsii.py

176 lines
7.2 KiB
Python

"""
GDSII file format readers and writers
"""
import gdsii.library
import gdsii.structure
import gdsii.elements
from typing import List, Any, Dict
import numpy
from .utils import mangle_name, make_dose_table
from .. import Pattern, SubPattern, PatternError
from ..shapes import Polygon
from ..utils import rotation_matrix_2d, get_bit, vector2
__author__ = 'Jan Petykiewicz'
def write_dose2dtype(pattern: Pattern,
filename: str,
meters_per_unit: float):
"""
Write a Pattern to a GDSII file, by first calling .polygonize() on it
to change the shapes into polygons, and then writing patterns as GDSII
structures, polygons as boundary elements, and subpatterns as structure
references (sref).
For each shape,
layer is chosen to be equal to shape.layer if it is an int,
or shape.layer[0] if it is a tuple
Note that this function modifies the Pattern.
It is often a good idea to run pattern.subpatternize() prior to calling this function,
especially if calling .polygonize() will result in very many vertices.
If you want pattern polygonized with non-default arguments, just call pattern.polygonize()
prior to calling this function.
:param pattern: A Pattern to write to file. Modified by this function.
:param filename: Filename to write to.
:param meters_per_unit: Written into the GDSII file, meters per length unit.
"""
# Create library
lib = gdsii.library.Library(version=600,
name='masque-write_dose2dtype'.encode('ASCII'),
logical_unit=1,
physical_unit=meters_per_unit)
# Get a dict of id(pattern) -> pattern
patterns_by_id = {**(pattern.referenced_patterns_by_id()), id(pattern): pattern}
# Get a table of (id(subpat.pattern), written_dose) for each subpattern
sd_table = make_dose_table(pattern)
# Figure out all the unique doses necessary to write this pattern
# This means going through each row in sd_table and adding the dose values needed to write
# that subpattern at that dose level
dose_vals = set()
for pat_id, pat_dose in sd_table:
pat = patterns_by_id[pat_id]
[dose_vals.add(shape.dose * pat_dose) for shape in pat.shapes]
if len(dose_vals) > 256:
raise PatternError('Too many dose values: {}, maximum 256 when using dtypes.'.format(len(dose_vals)))
dose_vals_list = list(dose_vals)
# Now create a structure for each row in sd_table (ie, each pattern + dose combination)
# and add in any Boundary and SREF elements
for pat_id, pat_dose in sd_table:
pat = patterns_by_id[pat_id]
structure = gdsii.structure.Structure(name=mangle_name(pat, pat_dose).encode('ASCII'))
lib.append(structure)
# Add a Boundary element for each shape
for shape in pat.shapes:
for polygon in shape.to_polygons():
data_type = dose_vals_list.index(polygon.dose * pat_dose)
xy_open = numpy.round(polygon.vertices + polygon.offset).astype(int)
xy_closed = numpy.vstack((xy_open, xy_open[0, :]))
if hasattr('__len__', polygon.layer):
layer = polygon.layer[0]
else:
layer = polygon.layer
structure.append(gdsii.elements.Boundary(layer=layer,
data_type=data_type,
xy=xy_closed))
# Add an SREF for each subpattern entry
# strans must be set for angle and mag to take effect
for subpat in pat.subpatterns:
dose_mult = subpat.dose * pat_dose
sref = gdsii.elements.SRef(struct_name=mangle_name(subpat.pattern, dose_mult).encode('ASCII'),
xy=numpy.round([subpat.offset]).astype(int))
sref.strans = 0
sref.angle = subpat.rotation
sref.mag = subpat.scale
structure.append(sref)
with open(filename, mode='wb') as stream:
lib.save(stream)
return dose_vals_list
def read_dtype2dose(filename: str) -> (List[Pattern], Dict[str, Any]):
"""
Read a gdsii file and translate it into a list of Pattern objects. GDSII structures are
translated into Pattern objects; boundaries are translated into polygons, and srefs and arefs
are translated into SubPattern objects.
:param filename: Filename specifying a GDSII file to read from.
:return: Tuple: (List of Patterns generated GDSII structures, Dict of GDSII library info)
"""
with open(filename, mode='rb') as stream:
lib = gdsii.library.Library.load(stream)
library_info = {'name': lib.name.decode('ASCII'),
'physical_unit': lib.physical_unit,
'logical_unit': lib.logical_unit,
}
def ref_element_to_subpat(element, offset: vector2) -> SubPattern:
# Helper function to create a SubPattern from an SREF or AREF. Sets subpat.pattern to None
# and sets the instance attribute .ref_name to the struct_name.
#
# BUG: Figure out what "absolute" means in the context of elements and if the current
# behavior is correct
# BUG: Need to check STRANS bit 0 to handle x-reflection
subpat = SubPattern(pattern=None, offset=offset)
subpat.ref_name = element.struct_name
if element.strans is not None:
if element.mag is not None:
subpat.scale = element.mag
# Bit 13 means absolute scale
if get_bit(element.strans, 13):
subpat.offset *= subpat.scale
if element.angle is not None:
subpat.rotation = element.angle
# Bit 14 means absolute rotation
if get_bit(element.strans, 14):
subpat.offset = numpy.dot(rotation_matrix_2d(subpat.rotation), subpat.offset)
return subpat
patterns = []
for structure in lib:
pat = Pattern(name=structure.name.decode('ASCII'))
for element in structure:
# Switch based on element type:
if isinstance(element, gdsii.elements.Boundary):
pat.shapes.append(
Polygon(vertices=element.xy[:-1],
dose=element.data_type,
layer=element.layer))
elif isinstance(element, gdsii.elements.SRef):
pat.subpatterns.append(ref_element_to_subpat(element, element.xy))
elif isinstance(element, gdsii.elements.ARef):
for offset in element.xy:
pat.subpatterns.append(ref_element_to_subpat(element, offset))
patterns.append(pat)
# Create a dict of {pattern.name: pattern, ...}, then fix up all subpattern.pattern entries
# according to the subpattern.ref_name (which is deleted after use).
patterns_dict = dict(((p.name, p) for p in patterns))
for p in patterns_dict.values():
for sp in p.subpatterns:
sp.pattern = patterns_dict[sp.ref_name.decode('ASCII')]
del sp.ref_name
return patterns_dict, library_info