Add all files to repository
This commit is contained in:
commit
5bf486ac81
19 changed files with 1998 additions and 0 deletions
3
masque/file/__init__.py
Normal file
3
masque/file/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
"""
|
||||
Functions for reading from and writing to various file formats.
|
||||
"""
|
||||
171
masque/file/gdsii.py
Normal file
171
masque/file/gdsii.py
Normal 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
139
masque/file/svg.py
Normal 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
41
masque/file/utils.py
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue