2020-05-11 19:09:35 -07:00
|
|
|
from typing import List, Dict, Optional
|
2019-05-17 00:41:43 -07:00
|
|
|
import copy
|
2016-03-15 19:12:39 -07:00
|
|
|
import numpy
|
|
|
|
from numpy import pi
|
|
|
|
|
|
|
|
from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS
|
|
|
|
from .. import PatternError
|
2020-05-11 18:39:02 -07:00
|
|
|
from ..utils import is_scalar, vector2, layer_t
|
2016-03-15 19:12:39 -07:00
|
|
|
|
|
|
|
|
|
|
|
class Circle(Shape):
|
|
|
|
"""
|
|
|
|
A circle, which has a position and radius.
|
|
|
|
"""
|
2019-05-17 00:37:56 -07:00
|
|
|
__slots__ = ('_radius', 'poly_num_points', 'poly_max_arclen')
|
|
|
|
_radius: float
|
2020-02-17 21:02:53 -08:00
|
|
|
""" Circle radius """
|
|
|
|
|
2020-05-11 19:09:35 -07:00
|
|
|
poly_num_points: Optional[int]
|
2020-02-17 21:02:53 -08:00
|
|
|
""" Sets the default number of points for `.polygonize()` """
|
|
|
|
|
2020-05-11 19:09:35 -07:00
|
|
|
poly_max_arclen: Optional[float]
|
2020-02-17 21:02:53 -08:00
|
|
|
""" Sets the default max segement length for `.polygonize()` """
|
2016-03-15 19:12:39 -07:00
|
|
|
|
|
|
|
# radius property
|
|
|
|
@property
|
|
|
|
def radius(self) -> float:
|
|
|
|
"""
|
|
|
|
Circle's radius (float, >= 0)
|
|
|
|
"""
|
|
|
|
return self._radius
|
|
|
|
|
|
|
|
@radius.setter
|
|
|
|
def radius(self, val: float):
|
|
|
|
if not is_scalar(val):
|
|
|
|
raise PatternError('Radius must be a scalar')
|
|
|
|
if not val >= 0:
|
|
|
|
raise PatternError('Radius must be non-negative')
|
|
|
|
self._radius = val
|
|
|
|
|
|
|
|
def __init__(self,
|
|
|
|
radius: float,
|
2020-05-11 19:09:35 -07:00
|
|
|
poly_num_points: Optional[int] = DEFAULT_POLY_NUM_POINTS,
|
|
|
|
poly_max_arclen: Optional[float] = None,
|
2019-05-17 00:39:46 -07:00
|
|
|
offset: vector2 = (0.0, 0.0),
|
2020-05-11 18:39:02 -07:00
|
|
|
layer: layer_t = 0,
|
2019-12-12 00:38:11 -08:00
|
|
|
dose: float = 1.0,
|
|
|
|
locked: bool = False):
|
2020-05-11 19:29:00 -07:00
|
|
|
object.__setattr__(self, 'locked', False)
|
2019-05-17 00:37:56 -07:00
|
|
|
self.identifier = ()
|
2016-03-15 19:12:39 -07:00
|
|
|
self.offset = numpy.array(offset, dtype=float)
|
|
|
|
self.layer = layer
|
|
|
|
self.dose = dose
|
|
|
|
self.radius = radius
|
|
|
|
self.poly_num_points = poly_num_points
|
|
|
|
self.poly_max_arclen = poly_max_arclen
|
2019-12-12 00:38:11 -08:00
|
|
|
self.locked = locked
|
2016-03-15 19:12:39 -07:00
|
|
|
|
2019-05-15 00:19:37 -07:00
|
|
|
def __deepcopy__(self, memo: Dict = None) -> 'Circle':
|
|
|
|
memo = {} if memo is None else memo
|
2019-12-13 21:16:43 -08:00
|
|
|
new = copy.copy(self).unlock()
|
2019-05-15 00:19:37 -07:00
|
|
|
new._offset = self._offset.copy()
|
2019-12-13 21:16:43 -08:00
|
|
|
new.locked = self.locked
|
2019-05-15 00:19:37 -07:00
|
|
|
return new
|
|
|
|
|
2019-05-17 00:39:46 -07:00
|
|
|
def to_polygons(self,
|
2020-05-11 19:09:35 -07:00
|
|
|
poly_num_points: Optional[int] = None,
|
|
|
|
poly_max_arclen: Optional[float] = None,
|
2019-05-17 00:39:46 -07:00
|
|
|
) -> List[Polygon]:
|
2016-03-15 19:12:39 -07:00
|
|
|
if poly_num_points is None:
|
|
|
|
poly_num_points = self.poly_num_points
|
|
|
|
if poly_max_arclen is None:
|
|
|
|
poly_max_arclen = self.poly_max_arclen
|
|
|
|
|
|
|
|
if (poly_num_points is None) and (poly_max_arclen is None):
|
|
|
|
raise PatternError('Number of points and arclength left '
|
|
|
|
'unspecified (default was also overridden)')
|
|
|
|
|
|
|
|
n = []
|
|
|
|
if poly_num_points is not None:
|
|
|
|
n += [poly_num_points]
|
|
|
|
if poly_max_arclen is not None:
|
|
|
|
n += [2 * pi * self.radius / poly_max_arclen]
|
2020-07-21 20:38:38 -07:00
|
|
|
num_points = int(round(max(n)))
|
|
|
|
thetas = numpy.linspace(2 * pi, 0, num_points, endpoint=False)
|
2016-03-15 19:12:39 -07:00
|
|
|
xs = numpy.cos(thetas) * self.radius
|
|
|
|
ys = numpy.sin(thetas) * self.radius
|
|
|
|
xys = numpy.vstack((xs, ys)).T
|
|
|
|
|
|
|
|
return [Polygon(xys, offset=self.offset, dose=self.dose, layer=self.layer)]
|
|
|
|
|
|
|
|
def get_bounds(self) -> numpy.ndarray:
|
|
|
|
return numpy.vstack((self.offset - self.radius,
|
|
|
|
self.offset + self.radius))
|
|
|
|
|
|
|
|
def rotate(self, theta: float) -> 'Circle':
|
|
|
|
return self
|
|
|
|
|
2018-04-14 15:27:56 -07:00
|
|
|
def mirror(self, axis: int) -> 'Circle':
|
|
|
|
self.offset *= -1
|
|
|
|
return self
|
|
|
|
|
2016-03-15 19:12:39 -07:00
|
|
|
def scale_by(self, c: float) -> 'Circle':
|
|
|
|
self.radius *= c
|
|
|
|
return self
|
|
|
|
|
|
|
|
def normalized_form(self, norm_value) -> normalized_shape_tuple:
|
|
|
|
rotation = 0.0
|
|
|
|
magnitude = self.radius / norm_value
|
|
|
|
return (type(self), self.layer), \
|
2019-05-17 00:41:26 -07:00
|
|
|
(self.offset, magnitude, rotation, False, self.dose), \
|
2016-03-15 19:12:39 -07:00
|
|
|
lambda: Circle(radius=norm_value, layer=self.layer)
|
|
|
|
|
2020-05-11 20:31:07 -07:00
|
|
|
def __repr__(self) -> str:
|
|
|
|
dose = f' d{self.dose:g}' if self.dose != 1 else ''
|
|
|
|
locked = ' L' if self.locked else ''
|
|
|
|
return f'<Circle l{self.layer} o{self.offset} r{self.radius:g}{dose}{locked}>'
|