diff --git a/masque/file/gdsii.py b/masque/file/gdsii.py index e05b4ff..8b4373f 100644 --- a/masque/file/gdsii.py +++ b/masque/file/gdsii.py @@ -404,7 +404,7 @@ def _aref_to_gridrep(element: gdsii.elements.ARef) -> GridRepetition: rotation = 0 offset = numpy.array(element.xy[0]) scale = 1 - mirror_signs = numpy.ones(2) + mirror_across_x = False if element.strans is not None: if element.mag is not None: @@ -419,15 +419,11 @@ def _aref_to_gridrep(element: gdsii.elements.ARef) -> GridRepetition: raise PatternError('Absolute rotation is not implemented yet!') # Bit 0 means mirror x-axis if get_bit(element.strans, 15 - 0): - mirror_signs[1] = -1 + mirror_across_x = True counts = [element.cols, element.rows] - vec_a0 = element.xy[1] - offset - vec_b0 = element.xy[2] - offset - - a_vector = numpy.dot(rotation_matrix_2d(-rotation), vec_a0 / scale / counts[0]) * mirror_signs - b_vector = numpy.dot(rotation_matrix_2d(-rotation), vec_b0 / scale / counts[1]) * mirror_signs - + a_vector = (element.xy[1] - offset) / counts[0] + b_vector = (element.xy[2] - offset) / counts[1] gridrep = GridRepetition(pattern=None, a_vector=a_vector, @@ -437,7 +433,7 @@ def _aref_to_gridrep(element: gdsii.elements.ARef) -> GridRepetition: offset=offset, rotation=rotation, scale=scale, - mirrored=(mirror_signs[::-1] == -1)) + mirrored=(mirror_across_x, False)) gridrep.identifier = element.struct_name return gridrep @@ -450,13 +446,12 @@ def _subpatterns_to_refs(subpatterns: List[SubPattern or GridRepetition] encoded_name = subpat.pattern.name # Note: GDS mirrors first and rotates second - mirror_x, extra_angle = normalize_mirror(subpat.mirrored) + mirror_across_x, extra_angle = normalize_mirror(subpat.mirrored) if isinstance(subpat, GridRepetition): - mirror_signs = [(-1 if mirror_x else 1), 1] xy = numpy.array(subpat.offset) + [ [0, 0], - numpy.dot(rotation_matrix_2d(subpat.rotation), subpat.a_vector * mirror_signs) * subpat.scale * subpat.a_count, - numpy.dot(rotation_matrix_2d(subpat.rotation), subpat.b_vector * mirror_signs) * subpat.scale * subpat.b_count, + subpat.a_vector * subpat.a_count, + subpat.b_vector * subpat.b_count, ] ref = gdsii.elements.ARef(struct_name=encoded_name, xy=numpy.round(xy).astype(int), @@ -468,7 +463,7 @@ def _subpatterns_to_refs(subpatterns: List[SubPattern or GridRepetition] ref.angle = ((subpat.rotation + extra_angle) * 180 / numpy.pi) % 360 # strans must be non-None for angle and mag to take effect - ref.strans = set_bit(0, 15 - 0, mirror_x) + ref.strans = set_bit(0, 15 - 0, mirror_across_x) ref.mag = subpat.scale refs.append(ref) diff --git a/masque/repetition.py b/masque/repetition.py index fb40270..f118f02 100644 --- a/masque/repetition.py +++ b/masque/repetition.py @@ -39,10 +39,14 @@ class GridRepetition: pattern: 'Pattern' _offset: numpy.ndarray - _rotation: float _dose: float + + _rotation: float + ''' Applies to individual instances in the grid, not the grid vectors ''' _scale: float + ''' Applies to individual instances in the grid, not the grid vectors ''' _mirrored: List[bool] + ''' Applies to individual instances in the grid, not the grid vectors ''' _a_vector: numpy.ndarray _b_vector: numpy.ndarray or None @@ -287,7 +291,7 @@ class GridRepetition: def rotate_around(self, pivot: vector2, rotation: float) -> 'GridRepetition': """ - Rotate around a point + Rotate the array around a point :param pivot: Point to rotate around :param rotation: Angle to rotate by (counterclockwise, radians) @@ -304,6 +308,19 @@ class GridRepetition: """ Rotate around (0, 0) + :param rotation: Angle to rotate by (counterclockwise, radians) + :return: self + """ + self.rotate_elements(rotation) + self.a_vector = numpy.dot(rotation_matrix_2d(rotation), self.a_vector) + if self.b_vector is not None: + self.b_vector = numpy.dot(rotation_matrix_2d(rotation), self.b_vector) + return self + + def rotate_elements(self, rotation: float) -> 'GridRepetition': + """ + Rotate each element around its origin + :param rotation: Angle to rotate by (counterclockwise, radians) :return: self """ @@ -317,13 +334,23 @@ class GridRepetition: :param axis: Axis to mirror across. :return: self """ - self.mirrored[axis] = not self.mirrored[axis] - self.rotation *= -1 + self.mirror_elements(axis) self.a_vector[axis] *= -1 if self.b_vector is not None: self.b_vector[axis] *= -1 return self + def mirror_elements(self, axis: int) -> 'GridRepetition': + """ + Mirror each element across an axis relative to its origin. + + :param axis: Axis to mirror across. + :return: self + """ + self.mirrored[axis] = not self.mirrored[axis] + self.rotation *= -1 + return self + def get_bounds(self) -> numpy.ndarray or None: """ Return a numpy.ndarray containing [[x_min, y_min], [x_max, y_max]], corresponding to the @@ -338,6 +365,18 @@ class GridRepetition: """ Scale the GridRepetition by a factor + :param c: scaling factor + """ + self.scale_elements_by(c) + self.a_vector *= c + if self.b_vector is not None: + self.b_vector *= c + return self + + def scale_elements_by(self, c: float) -> 'GridRepetition': + """ + Scale each element by a factor + :param c: scaling factor """ self.scale *= c