diff --git a/masque/repetition.py b/masque/repetition.py index 5707a50..5c9f105 100644 --- a/masque/repetition.py +++ b/masque/repetition.py @@ -170,8 +170,8 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots): @property def displacements(self) -> numpy.ndarray: aa, bb = numpy.meshgrid(numpy.arange(self.a_count), numpy.arange(self.b_count), indexing='ij') - return (aa.flat[:, None] * self.a_vector[None, :] + - bb.flat[:, None] * self.b_vector[None, :]) + return (aa.flatten()[:, None] * self.a_vector[None, :] + + bb.flatten()[:, None] * self.b_vector[None, :]) def rotate(self, rotation: float) -> 'Grid': """ diff --git a/masque/shapes/shape.py b/masque/shapes/shape.py index c6a0c63..7a5e3f3 100644 --- a/masque/shapes/shape.py +++ b/masque/shapes/shape.py @@ -31,16 +31,17 @@ class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable """ Abstract class specifying functions common to all shapes. """ + __slots__ = () # Children should use AutoSlots identifier: Tuple """ An arbitrary identifier for the shape, usually empty but used by `Pattern.flatten()` """ -# def __copy__(self) -> 'Shape': -# cls = self.__class__ -# new = cls.__new__(cls) -# for name in Shape.__slots__ + self.__slots__: -# object.__setattr__(new, name, getattr(self, name)) -# return new + def __copy__(self) -> 'Shape': + cls = self.__class__ + new = cls.__new__(cls) + for name in self.__slots__: + object.__setattr__(new, name, getattr(self, name)) + return new ''' --- Abstract methods diff --git a/masque/subpattern.py b/masque/subpattern.py index e898349..6964676 100644 --- a/masque/subpattern.py +++ b/masque/subpattern.py @@ -22,7 +22,7 @@ if TYPE_CHECKING: class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mirrorable, - Pivotable, Copyable, RepeatableImpl, LockableImpl, metaclass=AutoSlots): + PivotableImpl, Copyable, RepeatableImpl, LockableImpl, metaclass=AutoSlots): """ SubPattern provides basic support for nesting Pattern objects within each other, by adding offset, rotation, scaling, and associated methods. @@ -83,6 +83,7 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi dose=self.dose, scale=self.scale, mirrored=self.mirrored.copy(), + repetition=copy.deepcopy(self.repetition), locked=self.locked) return new @@ -90,6 +91,7 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi memo = {} if memo is None else memo new = copy.copy(self).unlock() new.pattern = copy.deepcopy(self.pattern, memo) + new.repetition = copy.deepcopy(self.repetition, memo) new.locked = self.locked return new @@ -140,18 +142,17 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi return pattern + def rotate(self, rotation: float) -> 'SubPattern': + self.rotation += rotation + if self.repetition is not None: + self.repetition.rotate(rotation) + return self + def mirror(self, axis: int) -> 'SubPattern': - """ - Mirror the subpattern across an axis. - - Args: - axis: Axis to mirror across. - - Returns: - self - """ self.mirrored[axis] = not self.mirrored[axis] self.rotation *= -1 + if self.repetition is not None: + self.repetiton.mirror(axis) return self def get_bounds(self) -> Optional[numpy.ndarray]: diff --git a/masque/traits/positionable.py b/masque/traits/positionable.py index 4db6880..d433a91 100644 --- a/masque/traits/positionable.py +++ b/masque/traits/positionable.py @@ -94,7 +94,7 @@ class PositionableImpl(Positionable, metaclass=ABCMeta): @offset.setter def offset(self, val: vector2): - if not isinstance(val, numpy.ndarray): + if not isinstance(val, numpy.ndarray) or val.dtype != numpy.float64: val = numpy.array(val, dtype=float) if val.size != 2: @@ -109,7 +109,6 @@ class PositionableImpl(Positionable, metaclass=ABCMeta): self.offset = offset return self - def translate(self: I, offset: vector2) -> I: self._offset += offset return self diff --git a/masque/traits/rotatable.py b/masque/traits/rotatable.py index e01c81d..64c2c51 100644 --- a/masque/traits/rotatable.py +++ b/masque/traits/rotatable.py @@ -109,6 +109,7 @@ class PivotableImpl(Pivotable, metaclass=ABCMeta): """ __slots__ = () + @abstractmethod def rotate_around(self: J, pivot: vector2, rotation: float) -> J: pivot = numpy.array(pivot, dtype=float) self.translate(-pivot) diff --git a/masque/utils.py b/masque/utils.py index 979ffde..2f2e499 100644 --- a/masque/utils.py +++ b/masque/utils.py @@ -148,11 +148,16 @@ class AutoSlots(ABCMeta): can be used to generate a full `__slots__` for the concrete class. """ def __new__(cls, name, bases, dctn): - slots = tuple(dctn.get('__slots__', tuple())) + parents = set() for base in bases: - if not hasattr(base, '__annotations__'): - continue - slots += tuple(getattr(base, '__annotations__').keys()) - dctn['__slots__'] = slots - return super().__new__(cls,name,bases,dctn) + parents |= set(base.mro()) + + slots = tuple(dctn.get('__slots__', tuple())) + for parent in parents: + if not hasattr(parent, '__annotations__'): + continue + slots += tuple(getattr(parent, '__annotations__').keys()) + + dctn['__slots__'] = slots + return super().__new__(cls, name, bases, dctn)