Implement SquareCustom end-caps and gds output
This commit is contained in:
parent
d5665f54a7
commit
bc43be48bc
@ -35,7 +35,7 @@ path_cap_map = {
|
||||
0: Path.Cap.Flush,
|
||||
1: Path.Cap.Circle,
|
||||
2: Path.Cap.Square,
|
||||
#3: custom?
|
||||
4: Path.Cap.SquareCustom,
|
||||
}
|
||||
|
||||
|
||||
@ -291,6 +291,13 @@ def read(stream: io.BufferedIOBase,
|
||||
'cap': cap,
|
||||
}
|
||||
|
||||
if cap == Path.Cap.SquareCustom:
|
||||
args['cap_extensions'] = numpy.zeros(2)
|
||||
if element.bgn_extn is not None:
|
||||
args['cap_extensions'][0] = element.bgn_extn
|
||||
if element.end_extn is not None:
|
||||
args['cap_extensions'][1] = element.end_extn
|
||||
|
||||
if use_dtype_as_dose:
|
||||
args['dose'] = element.data_type
|
||||
args['layer'] = element.layer
|
||||
|
@ -22,12 +22,14 @@ class Path(Shape):
|
||||
_vertices = None # type: numpy.ndarray
|
||||
_width = None # type: float
|
||||
_cap = None # type: Path.Cap
|
||||
_cap_extensions = None # type: numpy.ndarray or None
|
||||
|
||||
class Cap(Enum):
|
||||
Flush = 0 # Path ends at final vertices
|
||||
Circle = 1 # Path extends past final vertices with a semicircle of radius width/2
|
||||
Square = 2 # Path extends past final vertices with a width-by-width/2 rectangle
|
||||
|
||||
SquareCustom = 4 # Path extends past final vertices with a rectangle of length
|
||||
# defined by path.cap_extensions
|
||||
|
||||
# width property
|
||||
@property
|
||||
@ -59,7 +61,35 @@ class Path(Shape):
|
||||
|
||||
@cap.setter
|
||||
def cap(self, val: 'Path.Cap'):
|
||||
# TODO: Document that setting cap can change cap_extensions
|
||||
self._cap = Path.Cap(val)
|
||||
if self.cap != Path.Cap.SquareCustom:
|
||||
self.cap_extensions = None
|
||||
elif self.cap_extensions is None:
|
||||
# just got set to SquareCustom
|
||||
self.cap_extensions = numpy.zeros(2)
|
||||
|
||||
# cap_extensions property
|
||||
@property
|
||||
def cap_extensions(self) -> numpy.ndarray or None:
|
||||
"""
|
||||
Path end-cap extensionf
|
||||
|
||||
:return: 2-element ndarray or None
|
||||
"""
|
||||
return self._cap_extensions
|
||||
|
||||
@cap_extensions.setter
|
||||
def cap_extensions(self, vals: numpy.ndarray or None):
|
||||
custom_caps = (Path.Cap.SquareCustom,)
|
||||
if self.cap in custom_caps:
|
||||
if vals is None:
|
||||
raise Exception('Tried to set cap extensions to None on path with custom cap type')
|
||||
self._cap_extensions = numpy.array(vals, dtype=float)
|
||||
else:
|
||||
if vals is not None:
|
||||
raise Exception('Tried to set custom cap extensions on path with non-custom cap type')
|
||||
self._cap_extensions = vals
|
||||
|
||||
# vertices property
|
||||
@property
|
||||
@ -126,6 +156,8 @@ class Path(Shape):
|
||||
self.vertices = vertices
|
||||
self.width = width
|
||||
self.cap = cap
|
||||
if cap_extensions is not None:
|
||||
self.cap_extensions = cap_extensions
|
||||
self.rotate(rotation)
|
||||
[self.mirror(a) for a, do in enumerate(mirrored) if do]
|
||||
|
||||
@ -133,6 +165,7 @@ class Path(Shape):
|
||||
def travel(travel_pairs: Tuple[Tuple[float, float]],
|
||||
width: float = 0.0,
|
||||
cap: 'Path.Cap' = Cap.Flush,
|
||||
cap_extensions = None,
|
||||
offset: vector2=(0.0, 0.0),
|
||||
rotation: float = 0,
|
||||
mirrored: Tuple[bool] = (False, False),
|
||||
@ -149,6 +182,8 @@ class Path(Shape):
|
||||
to the +x axis).
|
||||
:param width: Path width, default 0
|
||||
:param cap: End-cap type, default Path.Cap.Flush (no end-cap)
|
||||
:param cap_extensions: End-cap extension distances, when using Path.Cap.CustomSquare.
|
||||
Default (0, 0) or None, depending on cap type
|
||||
:param offset: Offset, default (0, 0)
|
||||
:param rotation: Rotation counterclockwise, in radians. Default 0
|
||||
:param mirrored: Whether to mirror across the x or y axes. For example,
|
||||
@ -166,7 +201,7 @@ class Path(Shape):
|
||||
direction = numpy.dot(rotation_matrix_2d(angle), direction.T).T
|
||||
verts.append(verts[-1] + direction * distance)
|
||||
|
||||
return Path(vertices=verts, width=width, cap=cap,
|
||||
return Path(vertices=verts, width=width, cap=cap, cap_extensions=cap_extensions,
|
||||
offset=offset, rotation=rotation, mirrored=mirrored,
|
||||
layer=layer, dose=dose)
|
||||
|
||||
@ -174,12 +209,7 @@ class Path(Shape):
|
||||
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))
|
||||
extensions = self._calculate_cap_extensions()
|
||||
|
||||
v = remove_colinear_vertices(self.vertices, closed_path=False)
|
||||
dv = numpy.diff(v, axis=0)
|
||||
@ -191,13 +221,12 @@ class Path(Shape):
|
||||
|
||||
perp = dvdir[:, ::-1] * [[1, -1]] * self.width / 2
|
||||
|
||||
# add extension
|
||||
if extension != 0:
|
||||
v[0] -= dvdir[0] * extension
|
||||
v[-1] += dvdir[-1] * extension
|
||||
# add extensions
|
||||
if (extensions != 0).any():
|
||||
v[0] -= dvdir[0] * extensions[0]
|
||||
v[-1] += dvdir[-1] * extensions[1]
|
||||
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]
|
||||
@ -254,19 +283,17 @@ class Path(Shape):
|
||||
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 = self.width / 2
|
||||
Path.Cap.Square,
|
||||
Path.Cap.SquareCustom):
|
||||
extensions = self._calculate_cap_extensions()
|
||||
|
||||
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]] * self.width / 2
|
||||
|
||||
v[0] -= dvdir * extension
|
||||
v[-1] += dvdir * extension
|
||||
v[0] -= dvdir * extensions[0]
|
||||
v[-1] += dvdir * extensions[1]
|
||||
|
||||
bounds = self.offset + numpy.vstack((numpy.min(v - numpy.abs(perp), axis=0),
|
||||
numpy.max(v + numpy.abs(perp), axis=0)))
|
||||
@ -342,3 +369,14 @@ class Path(Shape):
|
||||
'''
|
||||
self.vertices = remove_colinear_vertices(self.vertices, closed_path=False)
|
||||
return self
|
||||
|
||||
def _calculate_cap_extensions(self) -> numpy.ndarray:
|
||||
if self.cap == Path.Cap.Square:
|
||||
extensions = numpy.full(2, self.width / 2)
|
||||
elif self.cap == Path.Cap.SquareCustom:
|
||||
extensions = self.cap_extensions
|
||||
else:
|
||||
# Flush or Circle
|
||||
extensions = numpy.zeros(2)
|
||||
return extensions
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user