Add all files to repository
This commit is contained in:
commit
5bf486ac81
19 changed files with 1998 additions and 0 deletions
182
masque/shapes/shape.py
Normal file
182
masque/shapes/shape.py
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
from typing import List, Tuple, Callable
|
||||
from abc import ABCMeta, abstractmethod
|
||||
import numpy
|
||||
|
||||
from .. import PatternError
|
||||
from ..utils import is_scalar, rotation_matrix_2d, vector2
|
||||
|
||||
|
||||
__author__ = 'Jan Petykiewicz'
|
||||
|
||||
|
||||
# Type definitions
|
||||
normalized_shape_tuple = Tuple[Tuple,
|
||||
Tuple[numpy.ndarray, float, float, float],
|
||||
Callable[[], 'Shape']]
|
||||
|
||||
# ## Module-wide defaults
|
||||
# Default number of points per polygon for shapes
|
||||
DEFAULT_POLY_NUM_POINTS = 24
|
||||
|
||||
|
||||
class Shape(object, metaclass=ABCMeta):
|
||||
"""
|
||||
Abstract class specifying functions common to all shapes.
|
||||
"""
|
||||
|
||||
# [x_offset, y_offset]
|
||||
_offset = numpy.array([0.0, 0.0]) # type: numpy.ndarray
|
||||
|
||||
# Layer (integer >= 0)
|
||||
_layer = 0 # type: int
|
||||
|
||||
# Dose
|
||||
_dose = 1.0 # type: float
|
||||
|
||||
# --- Abstract methods
|
||||
@abstractmethod
|
||||
def to_polygons(self, num_vertices: int, max_arclen: float) -> List['Polygon']:
|
||||
"""
|
||||
Returns a list of polygons which approximate the shape.
|
||||
|
||||
:param num_vertices: Number of points to use for each polygon. Can be overridden by
|
||||
max_arclen if that results in more points. Optional, defaults to shapes'
|
||||
internal defaults.
|
||||
:param max_arclen: Maximum arclength which can be approximated by a single line
|
||||
segment. Optional, defaults to shapes' internal defaults.
|
||||
:return: List of polygons equivalent to the shape
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_bounds(self) -> numpy.ndarray:
|
||||
"""
|
||||
Returns [[x_min, y_min], [x_max, y_max]] which specify a minimal bounding box for the shape.
|
||||
|
||||
:return: [[x_min, y_min], [x_max, y_max]]
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def rotate(self, theta: float) -> 'Shape':
|
||||
"""
|
||||
Rotate the shape around its center (0, 0), ignoring its offset.
|
||||
|
||||
:param theta: Angle to rotate by (counterclockwise, radians)
|
||||
:return: self
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def scale_by(self, c: float) -> 'Shape':
|
||||
"""
|
||||
Scale the shape's size (eg. radius, for a circle) by a constant factor.
|
||||
|
||||
:param c: Factor to scale by
|
||||
:return: self
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def normalized_form(self, norm_value: int) -> normalized_shape_tuple:
|
||||
"""
|
||||
Writes the shape in a standardized notation, with offset, scale, rotation, and dose
|
||||
information separated out from the remaining values.
|
||||
|
||||
:param norm_value: This value is used to normalize lengths intrinsic to teh shape;
|
||||
eg. for a circle, the returned magnitude value will be (radius / norm_value), and
|
||||
the returned callable will create a Circle(radius=norm_value, ...). This is useful
|
||||
when you find it important for quantities to remain in a certain range, eg. for
|
||||
GDSII where vertex locations are stored as integers.
|
||||
:return: The returned information takes the form of a 3-element tuple,
|
||||
(intrinsic, extrinsic, constructor). These are further broken down as:
|
||||
extrinsic: ([x_offset, y_offset], scale, rotation, dose)
|
||||
intrinsic: A tuple of basic types containing all information about the instance that
|
||||
is not contained in 'extrinsic'. Usually, intrinsic[0] == type(self).
|
||||
constructor: A callable (no arguments) which returns an instance of type(self) with
|
||||
internal state equivalent to 'intrinsic'.
|
||||
"""
|
||||
pass
|
||||
|
||||
# ---- Non-abstract properties
|
||||
# offset property
|
||||
@property
|
||||
def offset(self) -> numpy.ndarray:
|
||||
"""
|
||||
[x, y] offset
|
||||
|
||||
:return: [x_offset, y_offset]
|
||||
"""
|
||||
return self._offset
|
||||
|
||||
@offset.setter
|
||||
def offset(self, val: vector2):
|
||||
if not isinstance(val, numpy.ndarray):
|
||||
val = numpy.array(val, dtype=float)
|
||||
|
||||
if val.size != 2:
|
||||
raise PatternError('Offset must be convertible to size-2 ndarray')
|
||||
self._offset = val.flatten()
|
||||
|
||||
# layer property
|
||||
@property
|
||||
def layer(self) -> int:
|
||||
"""
|
||||
Layer number (int, >=0)
|
||||
|
||||
:return: Layer
|
||||
"""
|
||||
return self._layer
|
||||
|
||||
@layer.setter
|
||||
def layer(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise PatternError('Layer must be an integer')
|
||||
if not val >= 0:
|
||||
raise PatternError('Layer must be non-negative')
|
||||
self._layer = val
|
||||
|
||||
# dose property
|
||||
@property
|
||||
def dose(self) -> float:
|
||||
"""
|
||||
Dose (float >= 0)
|
||||
|
||||
:return: Dose value
|
||||
"""
|
||||
return self._dose
|
||||
|
||||
@dose.setter
|
||||
def dose(self, val: float):
|
||||
if not is_scalar(val):
|
||||
raise PatternError('Dose must be a scalar')
|
||||
if not val >= 0:
|
||||
raise PatternError('Dose must be non-negative')
|
||||
self._dose = val
|
||||
|
||||
# ---- Non-abstract methods
|
||||
def translate(self, offset: vector2) -> 'Shape':
|
||||
"""
|
||||
Translate the shape by the given offset
|
||||
|
||||
:param offset: [x_offset, y,offset]
|
||||
:return: self
|
||||
"""
|
||||
self.offset += offset
|
||||
return self
|
||||
|
||||
def rotate_around(self, pivot: vector2, rotation: float) -> 'Shape':
|
||||
"""
|
||||
Rotate the shape around a point.
|
||||
|
||||
:param pivot: Point (x, y) to rotate around
|
||||
:param rotation: Angle to rotate by (counterclockwise, radians)
|
||||
:return: self
|
||||
"""
|
||||
pivot = numpy.array(pivot, dtype=float)
|
||||
self.translate(-pivot)
|
||||
self.rotate(rotation)
|
||||
self.offset = numpy.dot(rotation_matrix_2d(rotation), self.offset)
|
||||
self.translate(+pivot)
|
||||
return self
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue