diff --git a/masque/pattern.py b/masque/pattern.py index a019a1b..f61ee4e 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -351,6 +351,40 @@ class Pattern: entry.rotate(rotation) return self + def mirror_element_centers(self, axis: int) -> 'Pattern': + """ + Mirror the offsets of all shapes and subpatterns across an axis + + :param axis: Axis to mirror across + :return: self + """ + for entry in self.shapes + self.subpatterns: + entry.offset[axis] *= -1 + return self + + def mirror_elements(self, axis: int) -> 'Pattern': + """ + Mirror each shape and subpattern across an axis, relative to its + center (offset) + + :param axis: Axis to mirror across + :return: self + """ + for entry in self.shapes + self.subpatterns: + entry.mirror(axis) + return self + + def mirror(self, axis: int) -> 'Pattern': + """ + Mirror the Pattern across an axis + + :param axis: Axis to mirror across + :return: self + """ + self.mirror_elements(axis) + self.mirror_element_centers(axis) + return self + def scale_element_doses(self, factor: float) -> 'Pattern': """ Multiply all shape and subpattern doses by a factor diff --git a/masque/subpattern.py b/masque/subpattern.py index 6242468..231d4e3 100644 --- a/masque/subpattern.py +++ b/masque/subpattern.py @@ -3,7 +3,7 @@ offset, rotation, scaling, and other such properties to the reference. """ -from typing import Union +from typing import Union, List import numpy from numpy import pi @@ -26,11 +26,13 @@ class SubPattern: _rotation = 0.0 # type: float _dose = 1.0 # type: float _scale = 1.0 # type: float + _mirrored = None # type: List[bool] def __init__(self, pattern: 'Pattern', offset: vector2=(0.0, 0.0), rotation: float=0.0, + mirrored: List[bool]=None, dose: float=1.0, scale: float=1.0): self.pattern = pattern @@ -38,6 +40,9 @@ class SubPattern: self.rotation = rotation self.dose = dose self.scale = scale + if mirrored is None: + mirrored = [False, False] + self.mirrored = mirrored # offset property @property @@ -90,6 +95,17 @@ class SubPattern: raise PatternError('Rotation must be a scalar') self._rotation = val % (2 * pi) + # Mirrored property + @property + def mirrored(self) -> List[bool]: + return self._mirrored + + @mirrored.setter + def mirrored(self, val: List[bool]): + if is_scalar(val): + raise PatternError('Mirrored must be a 2-element list of booleans') + self._mirrored = val + def as_pattern(self) -> 'Pattern': """ Returns a copy of self.pattern which has been scaled, rotated, etc. according to this @@ -98,6 +114,7 @@ class SubPattern: """ pattern = self.pattern.copy() pattern.scale_by(self.scale) + [pattern.mirror(ax) for ax, do in enumerate(self.mirrored) if do] pattern.rotate_around((0.0, 0.0), self.rotation) pattern.translate_elements(self.offset) pattern.scale_element_doses(self.dose) @@ -138,6 +155,16 @@ class SubPattern: self.rotation += rotation return self + def mirror(self, axis: int) -> 'SubPattern': + """ + Mirror the subpattern across an axis. + + :param axis: Axis to mirror across. + :return: self + """ + self.mirrored[axis] = not self.mirrored[axis] + 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