diff --git a/masque/file/dxf.py b/masque/file/dxf.py index 4faac8c..56b0bc5 100644 --- a/masque/file/dxf.py +++ b/masque/file/dxf.py @@ -299,14 +299,14 @@ def _subpatterns_to_refs(block: Union[ezdxf.layouts.BlockLayout, ezdxf.layouts.M rotated_a = rotation_matrix_2d(-subpat.rotation) @ a rotated_b = rotation_matrix_2d(-subpat.rotation) @ b if rotated_a[1] == 0 and rotated_b[0] == 0: - attribs['column_count'] = subpat.a_count - attribs['row_count'] = subpat.b_count + attribs['column_count'] = rep.a_count + attribs['row_count'] = rep.b_count attribs['column_spacing'] = rotated_a[0] attribs['row_spacing'] = rotated_b[1] block.add_blockref(encoded_name, subpat.offset, dxfattribs=attribs) elif rotated_a[0] == 0 and rotated_b[1] == 0: - attribs['column_count'] = subpat.b_count - attribs['row_count'] = subpat.a_count + attribs['column_count'] = rep.b_count + attribs['row_count'] = rep.a_count attribs['column_spacing'] = rotated_b[0] attribs['row_spacing'] = rotated_a[1] block.add_blockref(encoded_name, subpat.offset, dxfattribs=attribs) diff --git a/masque/file/gdsii.py b/masque/file/gdsii.py index 4c2198c..0be8e3e 100644 --- a/masque/file/gdsii.py +++ b/masque/file/gdsii.py @@ -365,8 +365,8 @@ def _ref_to_subpat(element: Union[gdsii.elements.SRef, if isinstance(element, gdsii.elements.ARef): a_count = element.cols b_count = element.rows - a_vector = (element.xy[1] - offset) / counts[0] - b_vector = (element.xy[2] - offset) / counts[1] + a_vector = (element.xy[1] - offset) / a_count + b_vector = (element.xy[2] - offset) / b_count repetition = Grid(a_vector=a_vector, b_vector=b_vector, a_count=a_count, b_count=b_count) @@ -389,9 +389,10 @@ def _subpatterns_to_refs(subpatterns: List[SubPattern] # Note: GDS mirrors first and rotates second mirror_across_x, extra_angle = normalize_mirror(subpat.mirrored) - ref: Union[gdsii.elements.SRef, gdsii.elements.ARef] - rep = subpat.repetition + + new_refs: List[Union[gdsii.elements.SRef, gdsii.elements.ARef]] + ref: Union[gdsii.elements.SRef, gdsii.elements.ARef] if isinstance(rep, Grid): xy = numpy.array(subpat.offset) + [ [0, 0], diff --git a/masque/file/oasis.py b/masque/file/oasis.py index 775cff7..981d1e0 100644 --- a/masque/file/oasis.py +++ b/masque/file/oasis.py @@ -469,7 +469,7 @@ def _placement_to_subpat(placement: fatrec.Placement) -> SubPattern: 'identifier': (name,), } - mrep: Repetition + mrep: Optional[Repetition] rep = placement.repetition if isinstance(rep, fatamorgana.GridRepetition): mrep = Grid(a_vector=rep.a_vector, @@ -477,8 +477,10 @@ def _placement_to_subpat(placement: fatrec.Placement) -> SubPattern: a_count=rep.a_count, b_count=rep.b_count) elif isinstance(rep, fatamorgana.ArbitraryRepetition): - mrep = Arbitrary(numpy.cumsum(numpy.column_stack((rep.x_displacements, - rep.y_displacements)))) + displacements = numpy.cumsum(numpy.column_stack((rep.x_displacements, + rep.y_displacements))) + displacements = numpy.vstack(([0, 0], displacements)) + mrep = Arbitrary(displacements) elif rep is None: mrep = None @@ -510,8 +512,10 @@ def _subpatterns_to_refs(subpatterns: List[SubPattern] b_count=numpy.round(rep.b_count).astype(int)) elif isinstance(rep, Arbitrary): diffs = numpy.diff(rep.displacements, axis=0) - args['repetition'] = fatamorgana.ArbitraryRepetition( - numpy.round(diffs).astype(int)) + diff_ints = numpy.round(diffs).astype(int) + args['repetition'] = fatamorgana.ArbitraryRepetition(diff_ints[:, 0], diff_ints[:, 1]) + args['x'] += rep.displacements[0, 0] + args['y'] += rep.displacements[0, 1] else: assert(rep is None) diff --git a/masque/repetition.py b/masque/repetition.py index 737fb33..5707a50 100644 --- a/masque/repetition.py +++ b/masque/repetition.py @@ -217,7 +217,7 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots): corners = ((0, 0), a_extent, b_extent, a_extent + b_extent) xy_min = numpy.min(corners, axis=0) - xy_max = numpy.min(corners, axis=0) + xy_max = numpy.max(corners, axis=0) return numpy.array((xy_min, xy_max)) def scale_by(self, c: float) -> 'Grid': @@ -298,9 +298,6 @@ class Arbitrary(LockableImpl, Repetition, metaclass=AutoSlots): of the instances. """ - locked: bool - """ If `True`, disallows changes to the object. """ - @property def displacements(self) -> numpy.ndarray: return self._displacements @@ -311,6 +308,18 @@ class Arbitrary(LockableImpl, Repetition, metaclass=AutoSlots): val = numpy.sort(val.view([('', val.dtype)] * val.shape[1]), 0).view(val.dtype) # sort rows self._displacements = val + def __init__(self, + displacements: numpy.ndarray, + locked: bool = False,): + """ + Args: + displacements: List of vectors (Nx2 ndarray) specifying displacements. + locked: Whether the object is locked after initialization. + """ + object.__setattr__(self, 'locked', False) + self.displacements = displacements + self.locked = locked + def lock(self) -> 'Arbitrary': """ Lock the object, disallowing changes. @@ -343,3 +352,56 @@ class Arbitrary(LockableImpl, Repetition, metaclass=AutoSlots): if self.locked != other.locked: return False return numpy.array_equal(self.displacements, other.displacements) + + def rotate(self, rotation: float) -> 'Arbitrary': + """ + Rotate dispacements (around (0, 0)) + + Args: + rotation: Angle to rotate by (counterclockwise, radians) + + Returns: + self + """ + self.displacements = numpy.dot(rotation_matrix_2d(rotation), self.displacements.T).T + return self + + def mirror(self, axis: int) -> 'Arbitrary': + """ + Mirror the displacements across an axis. + + Args: + axis: Axis to mirror across. + (0: mirror across x-axis, 1: mirror across y-axis) + + Returns: + self + """ + self.displacements[1-axis] *= -1 + return self + + def get_bounds(self) -> Optional[numpy.ndarray]: + """ + Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the + extent of the `displacements` in each dimension. + + Returns: + `[[x_min, y_min], [x_max, y_max]]` or `None` + """ + xy_min = numpy.min(self.displacements, axis=0) + xy_max = numpy.max(self.displacements, axis=0) + return numpy.array((xy_min, xy_max)) + + def scale_by(self, c: float) -> 'Arbitrary': + """ + Scale the displacements by a factor + + Args: + c: scaling factor + + Returns: + self + """ + self.displacements *= c + return self + diff --git a/masque/subpattern.py b/masque/subpattern.py index 7243e5e..e898349 100644 --- a/masque/subpattern.py +++ b/masque/subpattern.py @@ -35,27 +35,12 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi _pattern: Optional['Pattern'] """ The `Pattern` being instanced """ -# _offset: numpy.ndarray -# """ (x, y) offset for the instance """ - -# _rotation: float -# """ rotation for the instance, radians counterclockwise """ - -# _dose: float -# """ dose factor for the instance """ - -# _scale: float -# """ scale factor for the instance """ - _mirrored: numpy.ndarray # ndarray[bool] """ Whether to mirror the instance across the x and/or y axes. """ identifier: Tuple[Any, ...] """ Arbitrary identifier, used internally by some `masque` functions. """ -# locked: bool -# """ If `True`, disallows changes to the SubPattern""" - def __init__(self, pattern: Optional['Pattern'], offset: vector2 = (0.0, 0.0), @@ -79,7 +64,6 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi identifier: Arbitrary tuple, used internally by some `masque` functions. """ LockableImpl.unlock(self) -# object.__setattr__(self, 'locked', False) self.identifier = identifier self.pattern = pattern self.offset = offset @@ -146,9 +130,9 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi pattern.translate_elements(self.offset) pattern.scale_element_doses(self.dose) - if pattern.repetition is not None: - combined = type(pat)(name='__repetition__') - for dd in pattern.repetition.displacements: + if self.repetition is not None: + combined = type(pattern)(name='__repetition__') + for dd in self.repetition.displacements: temp_pat = pattern.deepcopy() temp_pat.translate_elements(dd) combined.append(temp_pat) diff --git a/masque/traits/doseable.py b/masque/traits/doseable.py index ded93fa..4b9daf3 100644 --- a/masque/traits/doseable.py +++ b/masque/traits/doseable.py @@ -28,10 +28,10 @@ class Doseable(metaclass=ABCMeta): """ pass - @dose.setter - @abstractmethod - def dose(self, val: float): - pass +# @dose.setter +# @abstractmethod +# def dose(self, val: float): +# pass ''' ---- Methods diff --git a/masque/traits/layerable.py b/masque/traits/layerable.py index 4cf0b1f..5382450 100644 --- a/masque/traits/layerable.py +++ b/masque/traits/layerable.py @@ -25,12 +25,12 @@ class Layerable(metaclass=ABCMeta): """ Layer number or name (int, tuple of ints, or string) """ - return self._layer + pass - @layer.setter - @abstractmethod - def layer(self, val: layer_t): - self._layer = val +# @layer.setter +# @abstractmethod +# def layer(self, val: layer_t): +# pass ''' ---- Methods diff --git a/masque/traits/lockable.py b/masque/traits/lockable.py index e0ea24e..cc12760 100644 --- a/masque/traits/lockable.py +++ b/masque/traits/lockable.py @@ -19,18 +19,6 @@ class Lockable(metaclass=ABCMeta): ''' ---- Methods ''' - def set_dose(self: T, dose: float) -> T: - """ - Set the dose - - Args: - dose: new value for dose - - Returns: - self - """ - pass - def lock(self: T) -> T: """ Lock the object, disallowing further changes diff --git a/masque/traits/mirrorable.py b/masque/traits/mirrorable.py index 76226c4..1e10800 100644 --- a/masque/traits/mirrorable.py +++ b/masque/traits/mirrorable.py @@ -6,7 +6,7 @@ import numpy from ..error import PatternError, PatternLockedError T = TypeVar('T', bound='Mirrorable') -T = TypeVar('T', bound='MirrorableImpl') +#I = TypeVar('I', bound='MirrorableImpl') class Mirrorable(metaclass=ABCMeta): diff --git a/masque/traits/positionable.py b/masque/traits/positionable.py index 3669a7e..4db6880 100644 --- a/masque/traits/positionable.py +++ b/masque/traits/positionable.py @@ -30,10 +30,10 @@ class Positionable(metaclass=ABCMeta): """ pass - @offset.setter - @abstractmethod - def offset(self, val: vector2): - pass +# @offset.setter +# @abstractmethod +# def offset(self, val: vector2): +# pass ''' --- Abstract methods diff --git a/masque/traits/repeatable.py b/masque/traits/repeatable.py index 1f2a99b..3971a94 100644 --- a/masque/traits/repeatable.py +++ b/masque/traits/repeatable.py @@ -1,4 +1,4 @@ -from typing import List, Tuple, Callable, TypeVar, Optional +from typing import List, Tuple, Callable, TypeVar, Optional, TYPE_CHECKING from abc import ABCMeta, abstractmethod import copy import numpy @@ -6,6 +6,10 @@ import numpy from ..error import PatternError, PatternLockedError +if TYPE_CHECKING: + from ..repetition import Repetition + + T = TypeVar('T', bound='Repeatable') I = TypeVar('I', bound='RepeatableImpl') @@ -27,14 +31,15 @@ class Repeatable(metaclass=ABCMeta): """ pass - @repetition.setter - @abstractmethod - def repetition(self, repetition: Optional['Repetition']): - pass +# @repetition.setter +# @abstractmethod +# def repetition(self, repetition: Optional['Repetition']): +# pass ''' ---- Methods ''' + @abstractmethod def set_repetition(self: T, repetition: Optional['Repetition']) -> T: """ Set the repetition @@ -74,6 +79,6 @@ class RepeatableImpl(Repeatable, metaclass=ABCMeta): ''' ---- Non-abstract methods ''' - def set_repetition(self: I, repetition: 'Repetition') -> I: + def set_repetition(self: I, repetition: Optional['Repetition']) -> I: self.repetition = repetition return self