diff --git a/masque/__init__.py b/masque/__init__.py index 53eab35..1555475 100644 --- a/masque/__init__.py +++ b/masque/__init__.py @@ -26,6 +26,7 @@ from .error import PatternError from .shapes import Shape +from .label import Label from .subpattern import SubPattern from .pattern import Pattern diff --git a/masque/file/gdsii.py b/masque/file/gdsii.py index 6bb95e2..61f20ab 100644 --- a/masque/file/gdsii.py +++ b/masque/file/gdsii.py @@ -11,7 +11,7 @@ import re import numpy from .utils import mangle_name, make_dose_table -from .. import Pattern, SubPattern, PatternError +from .. import Pattern, SubPattern, PatternError, Label from ..shapes import Polygon from ..utils import rotation_matrix_2d, get_bit, set_bit, vector2, is_scalar @@ -74,6 +74,7 @@ def write(patterns: Pattern or List[Pattern], structure = gdsii.structure.Structure(name=encoded_name) lib.append(structure) + # Add a Boundary element for each shape for shape in pat.shapes: layer, data_type = _mlayer2gds(shape.layer) @@ -83,6 +84,14 @@ def write(patterns: Pattern or List[Pattern], structure.append(gdsii.elements.Boundary(layer=layer, data_type=data_type, xy=xy_closed)) + for label in pat.labels: + layer, text_type = _mlayer2gds(label.layer) + xy_closed = numpy.round([label.offset, label.offset]).astype(int) + structure.append(gdsii.elements.Text(layer=layer, + text_type=text_type, + xy=xy_closed, + string=label.string.encode('ASCII'))) + # Add an SREF for each subpattern entry # strans must be set for angle and mag to take effect for subpat in pat.subpatterns: @@ -200,6 +209,14 @@ def write_dose2dtype(patterns: Pattern or List[Pattern], structure.append(gdsii.elements.Boundary(layer=layer, data_type=data_type, xy=xy_closed)) + for label in pat.labels: + layer, text_type = _mlayer2gds(label.layer) + xy_closed = numpy.round([label.offset, label.offset]).astype(int) + structure.append(gdsii.elements.Text(layer=layer, + text_type=text_type, + xy=xy_closed, + string=label.string.encode('ASCII'))) + # Add an SREF for each subpattern entry # strans must be set for angle and mag to take effect for subpat in pat.subpatterns: @@ -311,6 +328,12 @@ def read(filename: str, pat.shapes.append(shape) + elif isinstance(element, gdsii.elements.Text): + label = Label(offset=element.xy, + layer=(element.layer, element.text_type), + string=element.string.decode('ASCII')) + pat.labels.append(label) + elif isinstance(element, gdsii.elements.SRef): pat.subpatterns.append(ref_element_to_subpat(element, element.xy)) diff --git a/masque/pattern.py b/masque/pattern.py index f9d41f6..4c2807a 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -13,6 +13,7 @@ import numpy from .subpattern import SubPattern from .shapes import Shape, Polygon +from .label import Label from .utils import rotation_matrix_2d, vector2 from .error import PatternError @@ -32,11 +33,13 @@ class Pattern: :var name: An identifier for this object. Not necessarily unique. """ shapes = None # type: List[Shape] + labels = None # type: List[Labels] subpatterns = None # type: List[SubPattern] name = None # type: str def __init__(self, shapes: List[Shape]=(), + labels: List[Label]=(), subpatterns: List[SubPattern]=(), name: str='', ): @@ -45,6 +48,7 @@ class Pattern: Non-list inputs for shapes and subpatterns get converted to lists. :param shapes: Initial shapes in the Pattern + :param labels: Initial labels in the Pattern :param subpatterns: Initial subpatterns in the Pattern :param name: An identifier for the Pattern """ @@ -53,6 +57,11 @@ class Pattern: else: self.shapes = list(shapes) + if isinstance(labels, list): + self.labels = labels + else: + self.labels = list(labels) + if isinstance(subpatterns, list): self.subpatterns = subpatterns else: @@ -62,27 +71,32 @@ class Pattern: def append(self, other_pattern: 'Pattern') -> 'Pattern': """ - Appends all shapes and subpatterns from other_pattern to self's shapes and subpatterns. + Appends all shapes, labels and subpatterns from other_pattern to self's shapes, + labels, and supbatterns. :param other_pattern: The Pattern to append :return: self """ self.subpatterns += other_pattern.subpatterns self.shapes += other_pattern.shapes + self.labels += other_pattern.labels return self def subset(self, shapes_func: Callable[[Shape], bool]=None, + labels_func: Callable[[Label], bool]=None, subpatterns_func: Callable[[SubPattern], bool]=None, recursive: bool=False, ) -> 'Pattern': """ - Returns a Pattern containing only the shapes and subpatterns for which shapes_func or - subpatterns_func returns True. - Self is _not_ altered, but shapes and subpatterns are _not_ copied. + Returns a Pattern containing only the entities (e.g. shapes) for which the + given entity_func returns True. + Self is _not_ altered, but shapes, labels, and subpatterns are _not_ copied. :param shapes_func: Given a shape, returns a boolean denoting whether the shape is a member of the subset. Default always returns False. + :param labels_func: Given a label, returns a boolean denoting whether the label is a member + of the subset. Default always returns False. :param subpatterns_func: Given a subpattern, returns a boolean denoting if it is a member of the subset. Default always returns False. :param recursive: If True, also calls .subset() recursively on patterns referenced by this @@ -94,6 +108,8 @@ class Pattern: pat = Pattern(name=src.name) if shapes_func is not None: pat.shapes = [s for s in src.shapes if shapes_func(s)] + if labels_func is not None: + pat.labels = [s for s in src.labels if labels_func(s)] if subpatterns_func is not None: pat.subpatterns = [s for s in src.subpatterns if subpatterns_func(s)] return pat @@ -281,7 +297,7 @@ class Pattern: :return: [[x_min, y_min], [x_max, y_max]] or None """ - entries = self.shapes + self.subpatterns + entries = self.shapes + self.subpatterns + self.labels if not entries: return None @@ -304,17 +320,19 @@ class Pattern: self.subpatterns = [] for subpat in subpatterns: subpat.pattern.flatten() - self.shapes += subpat.as_pattern().shapes + p = subpat.as_pattern() + self.shapes += p.shapes + self.labels += p.labels return self def translate_elements(self, offset: vector2) -> 'Pattern': """ - Translates all shapes and subpatterns by the given offset. + Translates all shapes, label, and subpatterns by the given offset. :param offset: Offset to translate by :return: self """ - for entry in self.shapes + self.subpatterns: + for entry in self.shapes + self.subpatterns + self.labels: entry.translate(offset) return self @@ -359,12 +377,12 @@ class Pattern: def rotate_element_centers(self, rotation: float) -> 'Pattern': """ - Rotate the offsets of all shapes and subpatterns around (0, 0) + Rotate the offsets of all shapes, labels, and subpatterns around (0, 0) :param rotation: Angle to rotate by (counter-clockwise, radians) :return: self """ - for entry in self.shapes + self.subpatterns: + for entry in self.shapes + self.subpatterns + self.labels: entry.offset = numpy.dot(rotation_matrix_2d(rotation), entry.offset) return self @@ -381,12 +399,12 @@ class Pattern: def mirror_element_centers(self, axis: int) -> 'Pattern': """ - Mirror the offsets of all shapes and subpatterns across an axis + Mirror the offsets of all shapes, labels, and subpatterns across an axis :param axis: Axis to mirror across :return: self """ - for entry in self.shapes + self.subpatterns: + for entry in self.shapes + self.subpatterns + self.labels: entry.offset[axis - 1] *= -1 return self @@ -435,6 +453,7 @@ class Pattern: """ cp = copy.copy(self) cp.shapes = copy.deepcopy(cp.shapes) + cp.labels = copy.deepcopy(cp.labels) cp.subpatterns = [copy.copy(subpat) for subpat in cp.subpatterns] return cp @@ -487,6 +506,7 @@ class Pattern: :param fill_color: Interiors are drawn with this color (passed to matplotlib PolyCollection) :param overdraw: Whether to create a new figure or draw on a pre-existing one """ + # TODO: add text labels to visualize() from matplotlib import pyplot import matplotlib.collections