snapshot 2019-04-20 13:26:42.161005
This commit is contained in:
parent
60c2696fcd
commit
fa66e09f9b
@ -273,6 +273,7 @@ def read(filename: str,
|
|||||||
|
|
||||||
if isinstance(element, gdsii.elements.Path):
|
if isinstance(element, gdsii.elements.Path):
|
||||||
if element.path_type == 0:
|
if element.path_type == 0:
|
||||||
|
#cap = Path.Cap.Flush
|
||||||
extension = 0.0
|
extension = 0.0
|
||||||
elif element.path_type in (1, 4):
|
elif element.path_type in (1, 4):
|
||||||
raise PatternError('Round-ended and custom paths (types 1 and 4) are not implemented yet')
|
raise PatternError('Round-ended and custom paths (types 1 and 4) are not implemented yet')
|
||||||
|
@ -141,19 +141,21 @@ class Arc(Shape):
|
|||||||
radii: vector2,
|
radii: vector2,
|
||||||
angles: vector2,
|
angles: vector2,
|
||||||
width: float,
|
width: float,
|
||||||
rotation: float=0,
|
|
||||||
poly_num_points: int=DEFAULT_POLY_NUM_POINTS,
|
poly_num_points: int=DEFAULT_POLY_NUM_POINTS,
|
||||||
poly_max_arclen: float=None,
|
poly_max_arclen: float=None,
|
||||||
offset: vector2=(0.0, 0.0),
|
offset: vector2=(0.0, 0.0),
|
||||||
|
rotation: float=0,
|
||||||
|
mirrored: Tuple[bool] = (False, False),
|
||||||
layer: int=0,
|
layer: int=0,
|
||||||
dose: float=1.0):
|
dose: float=1.0):
|
||||||
self.offset = offset
|
|
||||||
self.layer = layer
|
|
||||||
self.dose = dose
|
|
||||||
self.radii = radii
|
self.radii = radii
|
||||||
self.angles = angles
|
self.angles = angles
|
||||||
self.width = width
|
self.width = width
|
||||||
|
self.offset = offset
|
||||||
self.rotation = rotation
|
self.rotation = rotation
|
||||||
|
[self.mirror(a) for a in a, do in enumerate(mirrored) if do]
|
||||||
|
self.layer = layer
|
||||||
|
self.dose = dose
|
||||||
self.poly_num_points = poly_num_points
|
self.poly_num_points = poly_num_points
|
||||||
self.poly_max_arclen = poly_max_arclen
|
self.poly_max_arclen = poly_max_arclen
|
||||||
|
|
||||||
@ -201,8 +203,7 @@ class Arc(Shape):
|
|||||||
ys = numpy.hstack((ys1, ys2))
|
ys = numpy.hstack((ys1, ys2))
|
||||||
xys = numpy.vstack((xs, ys)).T
|
xys = numpy.vstack((xs, ys)).T
|
||||||
|
|
||||||
poly = Polygon(xys, dose=self.dose, layer=self.layer, offset=self.offset)
|
poly = Polygon(xys, dose=self.dose, layer=self.layer, offset=self.offset, rotation=self.rotation)
|
||||||
poly.rotate(self.rotation)
|
|
||||||
return [poly]
|
return [poly]
|
||||||
|
|
||||||
def get_bounds(self) -> numpy.ndarray:
|
def get_bounds(self) -> numpy.ndarray:
|
||||||
@ -306,9 +307,9 @@ class Arc(Shape):
|
|||||||
rotation %= 2 * pi
|
rotation %= 2 * pi
|
||||||
width = self.width
|
width = self.width
|
||||||
|
|
||||||
return (type(self), radii, angles, width, self.layer), \
|
return (type(self), radii, angles, width/norm_value, self.layer), \
|
||||||
(self.offset, scale/norm_value, rotation, self.dose), \
|
(self.offset, scale/norm_value, rotation, self.dose), \
|
||||||
lambda: Arc(radii=radii*norm_value, angles=angles, width=width, layer=self.layer)
|
lambda: Arc(radii=radii*norm_value, angles=angles, width=width*norm_value, layer=self.layer)
|
||||||
|
|
||||||
def get_cap_edges(self) -> numpy.ndarray:
|
def get_cap_edges(self) -> numpy.ndarray:
|
||||||
'''
|
'''
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from typing import List
|
from typing import List, Tuple
|
||||||
import math
|
import math
|
||||||
import numpy
|
import numpy
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
@ -82,17 +82,19 @@ class Ellipse(Shape):
|
|||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
radii: vector2,
|
radii: vector2,
|
||||||
rotation: float=0,
|
|
||||||
poly_num_points: int=DEFAULT_POLY_NUM_POINTS,
|
poly_num_points: int=DEFAULT_POLY_NUM_POINTS,
|
||||||
poly_max_arclen: float=None,
|
poly_max_arclen: float=None,
|
||||||
offset: vector2=(0.0, 0.0),
|
offset: vector2=(0.0, 0.0),
|
||||||
|
rotation: float=0,
|
||||||
|
mirrored: Tuple[bool] = (False, False),
|
||||||
layer: int=0,
|
layer: int=0,
|
||||||
dose: float=1.0):
|
dose: float=1.0):
|
||||||
|
self.radii = radii
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
|
self.rotation = rotation
|
||||||
|
[self.mirror(a) for a in a, do in enumerate(mirrored) if do]
|
||||||
self.layer = layer
|
self.layer = layer
|
||||||
self.dose = dose
|
self.dose = dose
|
||||||
self.radii = radii
|
|
||||||
self.rotation = rotation
|
|
||||||
self.poly_num_points = poly_num_points
|
self.poly_num_points = poly_num_points
|
||||||
self.poly_max_arclen = poly_max_arclen
|
self.poly_max_arclen = poly_max_arclen
|
||||||
|
|
||||||
@ -129,8 +131,7 @@ class Ellipse(Shape):
|
|||||||
ys = r1 * sin_th
|
ys = r1 * sin_th
|
||||||
xys = numpy.vstack((xs, ys)).T
|
xys = numpy.vstack((xs, ys)).T
|
||||||
|
|
||||||
poly = Polygon(xys, dose=self.dose, layer=self.layer, offset=self.offset)
|
poly = Polygon(xys, dose=self.dose, layer=self.layer, offset=self.offset, rotation=self.rotation)
|
||||||
poly.rotate(self.rotation)
|
|
||||||
return [poly]
|
return [poly]
|
||||||
|
|
||||||
def get_bounds(self) -> numpy.ndarray:
|
def get_bounds(self) -> numpy.ndarray:
|
||||||
|
332
masque/shapes/path.py
Normal file
332
masque/shapes/path.py
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
from typing import List, Tuple
|
||||||
|
import copy
|
||||||
|
from enum import Enum
|
||||||
|
import numpy
|
||||||
|
from numpy import pi
|
||||||
|
|
||||||
|
from . import Shape, normalized_shape_tuple
|
||||||
|
from .. import PatternError
|
||||||
|
from ..utils import is_scalar, rotation_matrix_2d, vector2
|
||||||
|
from ..utils import remove_colinear_vertices, remove_duplicate_vertices
|
||||||
|
|
||||||
|
__author__ = 'Jan Petykiewicz'
|
||||||
|
|
||||||
|
|
||||||
|
class Path(Shape):
|
||||||
|
"""
|
||||||
|
A path, consisting of a bunch of vertices (Nx2 ndarray), a width, an end-cap shape,
|
||||||
|
and an offset.
|
||||||
|
|
||||||
|
A normalized_form(...) is available, but can be quite slow with lots of vertices.
|
||||||
|
"""
|
||||||
|
_vertices = None # type: numpy.ndarray
|
||||||
|
_width = None # type: float
|
||||||
|
_cap = None # type: Path.Cap
|
||||||
|
|
||||||
|
class Cap(Enum):
|
||||||
|
Flush = 0
|
||||||
|
Circle = 1
|
||||||
|
Square = 2
|
||||||
|
|
||||||
|
|
||||||
|
# width property
|
||||||
|
@property
|
||||||
|
def width(self) -> float:
|
||||||
|
"""
|
||||||
|
Path width (float, >= 0)
|
||||||
|
|
||||||
|
:return: width
|
||||||
|
"""
|
||||||
|
return self._width
|
||||||
|
|
||||||
|
@width.setter
|
||||||
|
def width(self, val: float):
|
||||||
|
if not is_scalar(val):
|
||||||
|
raise PatternError('Width must be a scalar')
|
||||||
|
if not val >= 0:
|
||||||
|
raise PatternError('Width must be non-negative')
|
||||||
|
self._width = val
|
||||||
|
|
||||||
|
# cap property
|
||||||
|
@property
|
||||||
|
def cap(self) -> 'Path.Cap':
|
||||||
|
"""
|
||||||
|
Path end-cap
|
||||||
|
|
||||||
|
:return: Path.Cap enum
|
||||||
|
"""
|
||||||
|
return self._cap
|
||||||
|
|
||||||
|
@cap.setter
|
||||||
|
def cap(self, val: 'Path.Cap'):
|
||||||
|
self._cap = Path.Cap(val)
|
||||||
|
|
||||||
|
# vertices property
|
||||||
|
@property
|
||||||
|
def vertices(self) -> numpy.ndarray:
|
||||||
|
"""
|
||||||
|
Vertices of the path (Nx2 ndarray: [[x0, y0], [x1, y1], ...]
|
||||||
|
|
||||||
|
:return: vertices
|
||||||
|
"""
|
||||||
|
return self._vertices
|
||||||
|
|
||||||
|
@vertices.setter
|
||||||
|
def vertices(self, val: numpy.ndarray):
|
||||||
|
val = numpy.array(val, dtype=float)
|
||||||
|
if len(val.shape) < 2 or val.shape[1] != 2:
|
||||||
|
raise PatternError('Vertices must be an Nx2 array')
|
||||||
|
if val.shape[0] < 2:
|
||||||
|
raise PatternError('Must have at least 2 vertices (Nx2 where N>1)')
|
||||||
|
self._vertices = val
|
||||||
|
|
||||||
|
# xs property
|
||||||
|
@property
|
||||||
|
def xs(self) -> numpy.ndarray:
|
||||||
|
"""
|
||||||
|
All vertex x coords as a 1D ndarray
|
||||||
|
"""
|
||||||
|
return self.vertices[:, 0]
|
||||||
|
|
||||||
|
@xs.setter
|
||||||
|
def xs(self, val: numpy.ndarray):
|
||||||
|
val = numpy.array(val, dtype=float).flatten()
|
||||||
|
if val.size != self.vertices.shape[0]:
|
||||||
|
raise PatternError('Wrong number of vertices')
|
||||||
|
self.vertices[:, 0] = val
|
||||||
|
|
||||||
|
# ys property
|
||||||
|
@property
|
||||||
|
def ys(self) -> numpy.ndarray:
|
||||||
|
"""
|
||||||
|
All vertex y coords as a 1D ndarray
|
||||||
|
"""
|
||||||
|
return self.vertices[:, 1]
|
||||||
|
|
||||||
|
@ys.setter
|
||||||
|
def ys(self, val: numpy.ndarray):
|
||||||
|
val = numpy.array(val, dtype=float).flatten()
|
||||||
|
if val.size != self.vertices.shape[0]:
|
||||||
|
raise PatternError('Wrong number of vertices')
|
||||||
|
self.vertices[:, 1] = val
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
vertices: numpy.ndarray,
|
||||||
|
width: float = 0.0,
|
||||||
|
cap: 'Path.Cap' = Path.Cap.Flush,
|
||||||
|
offset: vector2=(0.0, 0.0),
|
||||||
|
rotation: float = 0
|
||||||
|
mirrored: Tuple[bool] = (False, False),
|
||||||
|
layer: int=0,
|
||||||
|
dose: float=1.0,
|
||||||
|
) -> 'Path':
|
||||||
|
self.offset = offset
|
||||||
|
self.layer = layer
|
||||||
|
self.dose = dose
|
||||||
|
self.vertices = vertices
|
||||||
|
self.width = width
|
||||||
|
self.cap = cap
|
||||||
|
self.rotate(rotation)
|
||||||
|
[self.mirror(a) for a in a, do in enumerate(mirrored) if do]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def travel(travel_pairs: Tuple[Tuple[float, float]],
|
||||||
|
width: float = 0.0,
|
||||||
|
cap: 'Path.Cap' = Path.Cap.Flush,
|
||||||
|
offset: vector2=(0.0, 0.0),
|
||||||
|
rotation: float = 0
|
||||||
|
mirrored: Tuple[bool] = (False, False),
|
||||||
|
layer: int=0,
|
||||||
|
dose: float=1.0,
|
||||||
|
) -> 'Path':
|
||||||
|
"""
|
||||||
|
TODO
|
||||||
|
|
||||||
|
:param rotation: Rotation counterclockwise, in radians
|
||||||
|
:param offset: Offset, default (0, 0)
|
||||||
|
:param layer: Layer, default 0
|
||||||
|
:param dose: Dose, default 1.0
|
||||||
|
:return: A Polygon object containing the requested square
|
||||||
|
"""
|
||||||
|
direction = numpy.array([1, 0])
|
||||||
|
|
||||||
|
verts = [[0, 0]]
|
||||||
|
for angle, distance in travel_pairs:
|
||||||
|
direction = numpy.dot(rotation_matrix_2d(angle), direction.T).T
|
||||||
|
verts.append(verts[-1] + direction * distance)
|
||||||
|
|
||||||
|
return Path(vertices=verts, width=width, cap=cap,
|
||||||
|
offset=offset, rotation=rotation, mirrored=mirrored,
|
||||||
|
layer=layer, dose=dose)
|
||||||
|
|
||||||
|
def to_polygons(self,
|
||||||
|
poly_num_points: int=None,
|
||||||
|
poly_max_arclen: float=None,
|
||||||
|
) -> List['Polygon']:
|
||||||
|
if self.cap in (Path.Cap.Flush, Path.Cap.Circle
|
||||||
|
extension = 0.0
|
||||||
|
elif self.cap == Path.Cap.Square:
|
||||||
|
extension = self.width / 2
|
||||||
|
else:
|
||||||
|
raise PatternError('Unrecognized path endcap: {}'.format(self.cap))
|
||||||
|
|
||||||
|
v = remove_colinear_vertices(numpy.array(element.xy, dtype=float), closed_path=False)
|
||||||
|
dv = numpy.diff(v, axis=0)
|
||||||
|
dvdir = dv / numpy.sqrt((dv * dv).sum(axis=1))[:, None]
|
||||||
|
|
||||||
|
if self.width == 0:
|
||||||
|
verts = numpy.vstack((v, v[::-1]))
|
||||||
|
return [Polygon(offset=self.offset, vertices=verts, dose=self.dose, layer=self.layer)]
|
||||||
|
|
||||||
|
perp = dvdir[:, ::-1] * [[1, -1]] * self.width / 2
|
||||||
|
|
||||||
|
# add extension
|
||||||
|
if extension != 0
|
||||||
|
v[0] -= dvdir[0] * extension
|
||||||
|
v[-1] += dvdir[-1] * extension
|
||||||
|
dv = numpy.diff(v, axis=0) # recalculate dv; dvdir and perp should stay the same
|
||||||
|
|
||||||
|
|
||||||
|
# Find intersections of expanded sides
|
||||||
|
As = numpy.stack((dv[:-1], -dv[1:]), axis=2)
|
||||||
|
bs = v[1:-1] - v[:-2] + perp[1:] - perp[:-1]
|
||||||
|
ds = v[1:-1] - v[:-2] - perp[1:] + perp[:-1]
|
||||||
|
|
||||||
|
rp = numpy.linalg.solve(As, bs)[:, 0, None]
|
||||||
|
rn = numpy.linalg.solve(As, ds)[:, 0, None]
|
||||||
|
|
||||||
|
intersection_p = v[:-2] + rp * dv[:-1] + perp[:-1]
|
||||||
|
intersection_n = v[:-2] + rn * dv[:-1] - perp[:-1]
|
||||||
|
|
||||||
|
towards_perp = (dv[1:] * perp[:-1]).sum(axis=1) > 0 # path bends towards previous perp?
|
||||||
|
# straight = (dv[1:] * perp[:-1]).sum(axis=1) == 0 # path is straight
|
||||||
|
acute = (dv[1:] * dv[:-1]).sum(axis=1) < 0 # angle is acute?
|
||||||
|
|
||||||
|
# Build vertices
|
||||||
|
o0 = [v[0] + perp[0]]
|
||||||
|
o1 = [v[0] - perp[0]]
|
||||||
|
for i in range(dv.shape[0] - 1):
|
||||||
|
if towards_perp[i]:
|
||||||
|
o0.append(intersection_p[i])
|
||||||
|
if acute[i]:
|
||||||
|
o1.append(intersection_n[i])
|
||||||
|
else:
|
||||||
|
# Opposite is >270
|
||||||
|
pt0 = v[i + 1] - perp[i + 0] + dvdir[i + 0] * element.width / 2
|
||||||
|
pt1 = v[i + 1] - perp[i + 1] - dvdir[i + 1] * element.width / 2
|
||||||
|
o1 += [pt0, pt1]
|
||||||
|
else:
|
||||||
|
o1.append(intersection_n[i])
|
||||||
|
if acute[i]:
|
||||||
|
# > 270
|
||||||
|
pt0 = v[i + 1] + perp[i + 0] + dvdir[i + 0] * element.width / 2
|
||||||
|
pt1 = v[i + 1] + perp[i + 1] - dvdir[i + 1] * element.width / 2
|
||||||
|
o0 += [pt0, pt1]
|
||||||
|
else:
|
||||||
|
o0.append(intersection_p[i])
|
||||||
|
o0.append(v[-1] + perp[-1])
|
||||||
|
o1.append(v[-1] - perp[-1])
|
||||||
|
verts = numpy.vstack((o0, o1[::-1]))
|
||||||
|
|
||||||
|
polys = [Polygon(offset=self.offset, vertices=verts, dose=self.dose, layer=self.layer)]
|
||||||
|
|
||||||
|
if self.cap == Path.Cap.Circle:
|
||||||
|
for vert in v:
|
||||||
|
circ = Circle(offset=vert, radius=self.width / 2, dose=self.dose, layer=self.layer)
|
||||||
|
polys += circ.to_polygons(poly_num_points=poly_num_points, poly_max_arclen=poly_max_arclen)
|
||||||
|
|
||||||
|
return polys
|
||||||
|
|
||||||
|
def get_bounds(self) -> numpy.ndarray:
|
||||||
|
if self.cap == Path.Cap.Circle:
|
||||||
|
bounds = self.offset + numpy.vstack((numpy.min(self.vertices, axis=0) - self.width / 2,
|
||||||
|
numpy.max(self.vertices, axis=0) + self.width / 2))
|
||||||
|
elif self.cap in (Path.Cap.Flush,
|
||||||
|
Path.Cap.Square):
|
||||||
|
if self.cap == Path.Cap.Flush:
|
||||||
|
extension = 0
|
||||||
|
elif self.cap == Path.Cap.Square:
|
||||||
|
extension = element.width / 2
|
||||||
|
|
||||||
|
v = remove_colinear_vertices(self.vertices, closed_path=False)
|
||||||
|
dv = numpy.diff(v, axis=0)
|
||||||
|
dvdir = dv / numpy.sqrt((dv * dv).sum(axis=1))[:, None]
|
||||||
|
perp = dvdir[:, ::-1] * [[1, -1]] * element.width / 2
|
||||||
|
|
||||||
|
v[0] -= dvdir * extension
|
||||||
|
v[-1] += dvdir * extension
|
||||||
|
|
||||||
|
bounds = self.offset + numpy.vstack((numpy.min(v - numpy.abs(perp), axis=0),
|
||||||
|
numpy.max(v + numpy.abs(perp), axis=0)))
|
||||||
|
else:
|
||||||
|
raise PatternError('get_bounds() not implemented for endcaps: {}'.format(self.cap))
|
||||||
|
|
||||||
|
return bounds
|
||||||
|
|
||||||
|
def rotate(self, theta: float) -> 'Path':
|
||||||
|
self.vertices = numpy.dot(rotation_matrix_2d(theta), self.vertices.T).T
|
||||||
|
return self
|
||||||
|
|
||||||
|
def mirror(self, axis: int) -> 'Path':
|
||||||
|
self.vertices[:, axis - 1] *= -1
|
||||||
|
return self
|
||||||
|
|
||||||
|
def scale_by(self, c: float) -> 'Path':
|
||||||
|
self.vertices *= c
|
||||||
|
self.width *= c
|
||||||
|
return self
|
||||||
|
|
||||||
|
def normalized_form(self, norm_value: float) -> normalized_shape_tuple:
|
||||||
|
# Note: this function is going to be pretty slow for many-vertexed paths, relative to
|
||||||
|
# other shapes
|
||||||
|
offset = self.vertices.mean(axis=0) + self.offset
|
||||||
|
zeroed_vertices = self.vertices - offset
|
||||||
|
|
||||||
|
scale = zeroed_vertices.std()
|
||||||
|
normed_vertices = zeroed_vertices / scale
|
||||||
|
|
||||||
|
_, _, vertex_axis = numpy.linalg.svd(zeroed_vertices)
|
||||||
|
rotation = numpy.arctan2(vertex_axis[0][1], vertex_axis[0][0]) % (2 * pi)
|
||||||
|
rotated_vertices = numpy.vstack([numpy.dot(rotation_matrix_2d(-rotation), v)
|
||||||
|
for v in normed_vertices])
|
||||||
|
|
||||||
|
# Reorder the vertices so that the one with lowest x, then y, comes first.
|
||||||
|
x_min = rotated_vertices[:, 0].argmin()
|
||||||
|
if not is_scalar(x_min):
|
||||||
|
y_min = rotated_vertices[x_min, 1].argmin()
|
||||||
|
x_min = x_min[y_min]
|
||||||
|
reordered_vertices = numpy.roll(rotated_vertices, -x_min, axis=0)
|
||||||
|
|
||||||
|
width0 = self.width / norm_value
|
||||||
|
|
||||||
|
return (type(self), reordered_vertices.data.tobytes(), width0, self.cap, self.layer), \
|
||||||
|
(offset, scale/norm_value, rotation, self.dose), \
|
||||||
|
lambda: Polygon(reordered_vertices*norm_value, width=self.width*norm_value,
|
||||||
|
cap=self.cap, layer=self.layer)
|
||||||
|
|
||||||
|
def clean_vertices(self) -> 'Path':
|
||||||
|
"""
|
||||||
|
Removes duplicate, co-linear and otherwise redundant vertices.
|
||||||
|
|
||||||
|
:returns: self
|
||||||
|
"""
|
||||||
|
self.remove_colinear_vertices()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def remove_duplicate_vertices(self) -> 'Path':
|
||||||
|
'''
|
||||||
|
Removes all consecutive duplicate (repeated) vertices.
|
||||||
|
|
||||||
|
:returns: self
|
||||||
|
'''
|
||||||
|
self.vertices = remove_duplicate_vertices(self.vertices, closed_path=False)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def remove_colinear_vertices(self) -> 'Path':
|
||||||
|
'''
|
||||||
|
Removes consecutive co-linear vertices.
|
||||||
|
|
||||||
|
:returns: self
|
||||||
|
'''
|
||||||
|
self.vertices = remove_colinear_vertices(self.vertices, closed_path=False)
|
||||||
|
return self
|
@ -13,7 +13,8 @@ __author__ = 'Jan Petykiewicz'
|
|||||||
|
|
||||||
class Polygon(Shape):
|
class Polygon(Shape):
|
||||||
"""
|
"""
|
||||||
A polygon, consisting of a bunch of vertices (Nx2 ndarray) along with an offset.
|
A polygon, consisting of a bunch of vertices (Nx2 ndarray) which specify an
|
||||||
|
implicitly-closed boundary, and an offset.
|
||||||
|
|
||||||
A normalized_form(...) is available, but can be quite slow with lots of vertices.
|
A normalized_form(...) is available, but can be quite slow with lots of vertices.
|
||||||
"""
|
"""
|
||||||
@ -35,14 +36,14 @@ class Polygon(Shape):
|
|||||||
if len(val.shape) < 2 or val.shape[1] != 2:
|
if len(val.shape) < 2 or val.shape[1] != 2:
|
||||||
raise PatternError('Vertices must be an Nx2 array')
|
raise PatternError('Vertices must be an Nx2 array')
|
||||||
if val.shape[0] < 3:
|
if val.shape[0] < 3:
|
||||||
raise PatternError('Must have at least 3 vertices (Nx2, N>3)')
|
raise PatternError('Must have at least 3 vertices (Nx2 where N>2)')
|
||||||
self._vertices = val
|
self._vertices = val
|
||||||
|
|
||||||
# xs property
|
# xs property
|
||||||
@property
|
@property
|
||||||
def xs(self) -> numpy.ndarray:
|
def xs(self) -> numpy.ndarray:
|
||||||
"""
|
"""
|
||||||
All x vertices in a 1D ndarray
|
All vertex x coords as a 1D ndarray
|
||||||
"""
|
"""
|
||||||
return self.vertices[:, 0]
|
return self.vertices[:, 0]
|
||||||
|
|
||||||
@ -57,7 +58,7 @@ class Polygon(Shape):
|
|||||||
@property
|
@property
|
||||||
def ys(self) -> numpy.ndarray:
|
def ys(self) -> numpy.ndarray:
|
||||||
"""
|
"""
|
||||||
All y vertices in a 1D ndarray
|
All vertex y coords as a 1D ndarray
|
||||||
"""
|
"""
|
||||||
return self.vertices[:, 1]
|
return self.vertices[:, 1]
|
||||||
|
|
||||||
@ -71,12 +72,17 @@ class Polygon(Shape):
|
|||||||
def __init__(self,
|
def __init__(self,
|
||||||
vertices: numpy.ndarray,
|
vertices: numpy.ndarray,
|
||||||
offset: vector2=(0.0, 0.0),
|
offset: vector2=(0.0, 0.0),
|
||||||
|
rotation: float=0.0,
|
||||||
|
mirrored: Tuple[bool] = (False, False),
|
||||||
layer: int=0,
|
layer: int=0,
|
||||||
dose: float=1.0):
|
dose: float=1.0,
|
||||||
self.offset = offset
|
):
|
||||||
self.layer = layer
|
self.layer = layer
|
||||||
self.dose = dose
|
self.dose = dose
|
||||||
self.vertices = vertices
|
self.vertices = vertices
|
||||||
|
self.offset = offset
|
||||||
|
self.rotate(rotation)
|
||||||
|
[self.mirror(a) for a in a, do in enumerate(mirrored) if do]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def square(side_length: float,
|
def square(side_length: float,
|
||||||
|
@ -94,16 +94,16 @@ class Shape(metaclass=ABCMeta):
|
|||||||
Writes the shape in a standardized notation, with offset, scale, rotation, and dose
|
Writes the shape in a standardized notation, with offset, scale, rotation, and dose
|
||||||
information separated out from the remaining values.
|
information separated out from the remaining values.
|
||||||
|
|
||||||
:param norm_value: This value is used to normalize lengths intrinsic to teh shape;
|
:param norm_value: This value is used to normalize lengths intrinsic to the shape;
|
||||||
eg. for a circle, the returned magnitude value will be (radius / norm_value), and
|
eg. for a circle, the returned intrinsic radius value will be (radius / norm_value), and
|
||||||
the returned callable will create a Circle(radius=norm_value, ...). This is useful
|
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
|
when you find it important for quantities to remain in a certain range, eg. for
|
||||||
GDSII where vertex locations are stored as integers.
|
GDSII where vertex locations are stored as integers.
|
||||||
:return: The returned information takes the form of a 3-element tuple,
|
:return: The returned information takes the form of a 3-element tuple,
|
||||||
(intrinsic, extrinsic, constructor). These are further broken down as:
|
(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
|
intrinsic: A tuple of basic types containing all information about the instance that
|
||||||
is not contained in 'extrinsic'. Usually, intrinsic[0] == type(self).
|
is not contained in 'extrinsic'. Usually, intrinsic[0] == type(self).
|
||||||
|
extrinsic: ([x_offset, y_offset], scale, rotation, dose)
|
||||||
constructor: A callable (no arguments) which returns an instance of type(self) with
|
constructor: A callable (no arguments) which returns an instance of type(self) with
|
||||||
internal state equivalent to 'intrinsic'.
|
internal state equivalent to 'intrinsic'.
|
||||||
"""
|
"""
|
||||||
|
@ -61,15 +61,15 @@ class Text(Shape):
|
|||||||
def mirrored(self, val: List[bool]):
|
def mirrored(self, val: List[bool]):
|
||||||
if is_scalar(val):
|
if is_scalar(val):
|
||||||
raise PatternError('Mirrored must be a 2-element list of booleans')
|
raise PatternError('Mirrored must be a 2-element list of booleans')
|
||||||
self._mirrored = val
|
self._mirrored = list(val)
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
string: str,
|
string: str,
|
||||||
height: float,
|
height: float,
|
||||||
font_path: str,
|
font_path: str,
|
||||||
mirrored: List[bool]=None,
|
|
||||||
rotation: float=0.0,
|
|
||||||
offset: vector2=(0.0, 0.0),
|
offset: vector2=(0.0, 0.0),
|
||||||
|
rotation: float=0.0,
|
||||||
|
mirrored: Tuple[bool]=(False, False),
|
||||||
layer: int=0,
|
layer: int=0,
|
||||||
dose: float=1.0):
|
dose: float=1.0):
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
@ -79,8 +79,6 @@ class Text(Shape):
|
|||||||
self.height = height
|
self.height = height
|
||||||
self.rotation = rotation
|
self.rotation = rotation
|
||||||
self.font_path = font_path
|
self.font_path = font_path
|
||||||
if mirrored is None:
|
|
||||||
mirrored = [False, False]
|
|
||||||
self.mirrored = mirrored
|
self.mirrored = mirrored
|
||||||
|
|
||||||
def to_polygons(self,
|
def to_polygons(self,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user