Add all files to repository

This commit is contained in:
jan 2016-03-15 19:12:39 -07:00
commit 5bf486ac81
19 changed files with 1998 additions and 0 deletions

3
masque/file/__init__.py Normal file
View file

@ -0,0 +1,3 @@
"""
Functions for reading from and writing to various file formats.
"""

171
masque/file/gdsii.py Normal file
View file

@ -0,0 +1,171 @@
"""
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).
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)
# Polygonize pattern
pattern.polygonize()
# 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, :]))
structure.append(gdsii.elements.Boundary(layer=polygon.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

139
masque/file/svg.py Normal file
View file

@ -0,0 +1,139 @@
"""
SVG file format readers and writers
"""
import svgwrite
import numpy
from .utils import mangle_name
from .. import Pattern
__author__ = 'Jan Petykiewicz'
def write(pattern: Pattern,
filename: str,
custom_attributes: bool=False):
"""
Write a Pattern to an SVG file, by first calling .polygonize() on it
to change the shapes into polygons, and then writing patterns as SVG
groups (<g>, inside <defs>), polygons as paths (<path>), and subpatterns
as <use> elements.
Note that this function modifies the Pattern.
If custom_attributes is True, non-standard pattern_layer and pattern_dose attributes
are written to the relevant elements.
It is often a good idea to run pattern.subpatternize() on pattern 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: Pattern to write to file. Modified by this function.
:param filename: Filename to write to.
:param custom_attributes: Whether to write non-standard pattern_layer and
pattern_dose attributes to the SVG elements.
"""
# Polygonize pattern
pattern.polygonize()
[bounds_min, bounds_max] = pattern.get_bounds()
viewbox = numpy.hstack((bounds_min - 1, (bounds_max - bounds_min) + 2))
viewbox_string = '{:g} {:g} {:g} {:g}'.format(*viewbox)
# Create file
svg = svgwrite.Drawing(filename, profile='full', viewBox=viewbox_string,
debug=(not custom_attributes))
# Get a dict of id(pattern) -> pattern
patterns_by_id = {**(pattern.referenced_patterns_by_id()), id(pattern): pattern}
# Now create a group for each row in sd_table (ie, each pattern + dose combination)
# and add in any Boundary and Use elements
for pat in patterns_by_id.values():
svg_group = svg.g(id=mangle_name(pat), fill='blue', stroke='red')
for shape in pat.shapes:
for polygon in shape.to_polygons():
path_spec = poly2path(polygon.vertices + polygon.offset)
path = svg.path(d=path_spec)
if custom_attributes:
path['pattern_layer'] = polygon.layer
path['pattern_dose'] = polygon.dose
svg_group.add(path)
for subpat in pat.subpatterns:
transform = 'scale({:g}) rotate({:g}) translate({:g},{:g})'.format(
subpat.scale, subpat.rotation, subpat.offset[0], subpat.offset[1])
use = svg.use(href='#' + mangle_name(subpat.pattern), transform=transform)
if custom_attributes:
use['pattern_dose'] = subpat.dose
svg_group.add(use)
svg.defs.add(svg_group)
svg.add(svg.use(href='#' + mangle_name(pattern)))
svg.save()
def write_inverted(pattern: Pattern, filename: str):
"""
Write an inverted Pattern to an SVG file, by first calling .polygonize() and
.flatten() on it to change the shapes into polygons, then drawing a bounding
box and drawing the polygons with reverse vertex order inside it, all within
one <path> element.
Note that this function modifies the Pattern.
If you want pattern polygonized with non-default arguments, just call pattern.polygonize()
prior to calling this function.
:param pattern: Pattern to write to file. Modified by this function.
:param filename: Filename to write to.
"""
# Polygonize and flatten pattern
pattern.polygonize().flatten()
[bounds_min, bounds_max] = pattern.get_bounds()
viewbox = numpy.hstack((bounds_min - 1, (bounds_max - bounds_min) + 2))
viewbox_string = '{:g} {:g} {:g} {:g}'.format(*viewbox)
# Create file
svg = svgwrite.Drawing(filename, profile='full', viewBox=viewbox_string)
# Draw bounding box
slab_edge = [[bounds_min[0] - 1, bounds_max[1] + 1],
[bounds_max[0] + 1, bounds_max[1] + 1],
[bounds_max[0] + 1, bounds_min[1] - 1],
[bounds_min[0] - 1, bounds_min[1] - 1]]
path_spec = poly2path(slab_edge)
# Draw polygons with reversed vertex order
for shape in pattern.shapes:
for polygon in shape.to_polygons():
path_spec += poly2path(polygon.vertices[::-1] + polygon.offset)
svg.add(svg.path(d=path_spec, fill='blue', stroke='red'))
svg.save()
def poly2path(vertices: numpy.ndarray) -> str:
"""
Create an SVG path string from an Nx2 list of vertices.
:param vertices: Nx2 array of vertices.
:return: SVG path-string.
"""
commands = 'M{:g},{:g} '.format(vertices[0][0], vertices[0][1])
for vertex in vertices[1:]:
commands += 'L{:g},{:g}'.format(vertex[0], vertex[1])
commands += ' Z '
return commands

41
masque/file/utils.py Normal file
View file

@ -0,0 +1,41 @@
"""
Helper functions for file reading and writing
"""
import re
from typing import Set, Tuple
from masque.pattern import Pattern
__author__ = 'Jan Petykiewicz'
def mangle_name(pattern: Pattern, dose_multiplier: float=1.0) -> str:
"""
Create a name using pattern.name, id(pattern), and the dose multiplier.
:param pattern: Pattern whose name we want to mangle.
:param dose_multiplier: Dose multiplier to mangle with.
:return: Mangled name.
"""
expression = re.compile('[^A-Za-z0-9_\?\$]')
sanitized_name = expression.sub('_', pattern.name)
full_name = '{}_{}_{}'.format(sanitized_name, dose_multiplier, id(pattern))
return full_name
def make_dose_table(pattern: Pattern, dose_multiplier: float=1.0) -> Set[Tuple[int, float]]:
"""
Create a set containing (id(subpat.pattern), written_dose) for each subpattern
:param pattern: Source Pattern.
:param dose_multiplier: Multiplier for all written_dose entries.
:return: {(id(subpat.pattern), written_dose), ...}
"""
dose_table = {(id(pattern), dose_multiplier)}
for subpat in pattern.subpatterns:
subpat_dose_entry = (id(subpat.pattern), subpat.dose * dose_multiplier)
if subpat_dose_entry not in dose_table:
subpat_dose_table = make_dose_table(subpat.pattern, subpat.dose * dose_multiplier)
dose_table = dose_table.union(subpat_dose_table)
return dose_table