diff --git a/masque/file/gdsii.py b/masque/file/gdsii.py index 649a15d..8d120c7 100644 --- a/masque/file/gdsii.py +++ b/masque/file/gdsii.py @@ -19,14 +19,14 @@ from ..utils import rotation_matrix_2d, get_bit, vector2 __author__ = 'Jan Petykiewicz' -def write(pattern: Pattern, +def write(patterns: Pattern or List[Pattern], filename: str, meters_per_unit: float, logical_units_per_unit: float = 1): """ - 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 + Write a Pattern or list of patterns to a GDSII file, by first calling + .polygonize() 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, @@ -43,7 +43,7 @@ def write(pattern: Pattern, 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 patterns: A Pattern or list of patterns 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 (database) length unit. All distances are assumed to be an integer multiple of this unit, and are stored as such. @@ -57,12 +57,17 @@ def write(pattern: Pattern, logical_unit=logical_units_per_unit, physical_unit=meters_per_unit) + if isinstance(patterns, Pattern): + patterns = [patterns] + # Get a dict of id(pattern) -> pattern - patterns_by_id = {**(pattern.referenced_patterns_by_id()), id(pattern): pattern} + patterns_by_id = {id(pattern): pattern for pattern in patterns} + for pattern in patterns: + patterns_by_id.update(pattern.referenced_patterns_by_id()) # Now create a structure for each pattern, and add in any Boundary and SREF elements for pat in patterns_by_id.values(): - sanitized_name = re.compile('[^A-Za-z0-9_\?\$]').sub('_', pattern.name) + sanitized_name = re.compile('[^A-Za-z0-9_\?\$]').sub('_', pat.name) structure = gdsii.structure.Structure(name=sanitized_name.encode('ASCII')) lib.append(structure) @@ -86,7 +91,7 @@ def write(pattern: Pattern, # Add an SREF for each subpattern entry # strans must be set for angle and mag to take effect for subpat in pat.subpatterns: - sanitized_name = re.compile('[^A-Za-z0-9_\?\$]').sub('_', subpat.name) + sanitized_name = re.compile('[^A-Za-z0-9_\?\$]').sub('_', subpat.pattern.name) sref = gdsii.elements.SRef(struct_name=sanitized_name.encode('ASCII'), xy=numpy.round([subpat.offset]).astype(int)) sref.strans = 0 @@ -98,15 +103,15 @@ def write(pattern: Pattern, lib.save(stream) -def write_dose2dtype(pattern: Pattern, +def write_dose2dtype(patterns: Pattern or List[Pattern], filename: str, meters_per_unit: float, logical_units_per_unit: float = 1 ) -> List[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 + Write a Pattern or list of patterns to a GDSII file, by first calling + .polygonize() 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, @@ -117,7 +122,7 @@ def write_dose2dtype(pattern: Pattern, A list of doses is retured, providing a mapping between datatype (list index) and dose (list entry). - Note that this function modifies the Pattern. + Note that this function modifies the Pattern(s). 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. @@ -125,7 +130,7 @@ def write_dose2dtype(pattern: Pattern, 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 patterns: A Pattern or list of patterns 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 (database) length unit. All distances are assumed to be an integer multiple of this unit, and are stored as such. @@ -141,11 +146,16 @@ def write_dose2dtype(pattern: Pattern, logical_unit=logical_units_per_unit, physical_unit=meters_per_unit) - # Get a dict of id(pattern) -> pattern - patterns_by_id = {**(pattern.referenced_patterns_by_id()), id(pattern): pattern} + if isinstance(patterns, Pattern): + patterns = [patterns] - # Get a table of (id(subpat.pattern), written_dose) for each subpattern - sd_table = make_dose_table(pattern) + # Get a dict of id(pattern) -> pattern + patterns_by_id = {id(pattern): pattern for pattern in patterns} + for pattern in patterns: + patterns_by_id.update(pattern.referenced_patterns_by_id()) + + # Get a table of (id(pat), written_dose) for each pattern and subpattern + sd_table = make_dose_table(patterns) # 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 @@ -185,7 +195,7 @@ def write_dose2dtype(pattern: Pattern, # 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'), + 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 diff --git a/masque/file/utils.py b/masque/file/utils.py index e71e500..97e3d36 100644 --- a/masque/file/utils.py +++ b/masque/file/utils.py @@ -2,7 +2,7 @@ Helper functions for file reading and writing """ import re -from typing import Set, Tuple +from typing import Set, Tuple, List from masque.pattern import Pattern @@ -24,18 +24,19 @@ def mangle_name(pattern: Pattern, dose_multiplier: float=1.0) -> str: return sanitized_name -def make_dose_table(pattern: Pattern, dose_multiplier: float=1.0) -> Set[Tuple[int, float]]: +def make_dose_table(patterns: List[Pattern], dose_multiplier: float=1.0) -> Set[Tuple[int, float]]: """ - Create a set containing (id(subpat.pattern), written_dose) for each subpattern + Create a set containing (id(pat), written_dose) for each pattern (including subpatterns) - :param pattern: Source Pattern. + :param pattern: Source Patterns. :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) + dose_table = {(id(pattern), dose_multiplier) for pattern in patterns} + for pattern in patterns: + 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