Docstring format change
(new param and return format) Also some minor code formatting fixes in utils
This commit is contained in:
parent
20981f10b9
commit
5adabfd25a
16 changed files with 844 additions and 496 deletions
|
|
@ -24,19 +24,28 @@ class Arc(Shape):
|
|||
__slots__ = ('_radii', '_angles', '_width', '_rotation',
|
||||
'poly_num_points', 'poly_max_arclen')
|
||||
_radii: numpy.ndarray
|
||||
_angles: numpy.ndarray
|
||||
_width: float
|
||||
""" Two radii for defining an ellipse """
|
||||
|
||||
_rotation: float
|
||||
""" Rotation (ccw, radians) from the x axis to the first radius """
|
||||
|
||||
_angles: numpy.ndarray
|
||||
""" Start and stop angles (ccw, radians) for choosing an arc from the ellipse, measured from the first radius """
|
||||
|
||||
_width: float
|
||||
""" Width of the arc """
|
||||
|
||||
poly_num_points: int
|
||||
""" Sets the default number of points for `.polygonize()` """
|
||||
|
||||
poly_max_arclen: float
|
||||
""" Sets the default max segement length for `.polygonize()` """
|
||||
|
||||
# radius properties
|
||||
@property
|
||||
def radii(self) -> numpy.ndarray:
|
||||
"""
|
||||
Return the radii [rx, ry]
|
||||
|
||||
:return: [rx, ry]
|
||||
Return the radii `[rx, ry]`
|
||||
"""
|
||||
return self._radii
|
||||
|
||||
|
|
@ -73,10 +82,11 @@ class Arc(Shape):
|
|||
@property
|
||||
def angles(self) -> vector2:
|
||||
"""
|
||||
Return the start and stop angles [a_start, a_stop].
|
||||
Return the start and stop angles `[a_start, a_stop]`.
|
||||
Angles are measured from x-axis after rotation
|
||||
|
||||
:return: [a_start, a_stop]
|
||||
Returns:
|
||||
`[a_start, a_stop]`
|
||||
"""
|
||||
return self._angles
|
||||
|
||||
|
|
@ -109,7 +119,8 @@ class Arc(Shape):
|
|||
"""
|
||||
Rotation of radius_x from x_axis, counterclockwise, in radians. Stored mod 2*pi
|
||||
|
||||
:return: rotation counterclockwise in radians
|
||||
Returns:
|
||||
rotation counterclockwise in radians
|
||||
"""
|
||||
return self._rotation
|
||||
|
||||
|
|
@ -125,7 +136,8 @@ class Arc(Shape):
|
|||
"""
|
||||
Width of the arc (difference between inner and outer radii)
|
||||
|
||||
:return: width
|
||||
Returns:
|
||||
width
|
||||
"""
|
||||
return self._width
|
||||
|
||||
|
|
@ -225,12 +237,12 @@ class Arc(Shape):
|
|||
def get_bounds(self) -> numpy.ndarray:
|
||||
'''
|
||||
Equation for rotated ellipse is
|
||||
x = x0 + a * cos(t) * cos(rot) - b * sin(t) * sin(phi)
|
||||
y = y0 + a * cos(t) * sin(rot) + b * sin(t) * cos(rot)
|
||||
where t is our parameter.
|
||||
`x = x0 + a * cos(t) * cos(rot) - b * sin(t) * sin(phi)`
|
||||
`y = y0 + a * cos(t) * sin(rot) + b * sin(t) * cos(rot)`
|
||||
where `t` is our parameter.
|
||||
|
||||
Differentiating and solving for 0 slope wrt. t, we find
|
||||
tan(t) = -+ b/a cot(phi)
|
||||
Differentiating and solving for 0 slope wrt. `t`, we find
|
||||
`tan(t) = -+ b/a cot(phi)`
|
||||
where -+ is for x, y cases, so that's where the extrema are.
|
||||
|
||||
If the extrema are innaccessible due to arc constraints, check the arc endpoints instead.
|
||||
|
|
@ -329,8 +341,11 @@ class Arc(Shape):
|
|||
|
||||
def get_cap_edges(self) -> numpy.ndarray:
|
||||
'''
|
||||
:returns: [[[x0, y0], [x1, y1]], array of 4 points, specifying the two cuts which
|
||||
[[x2, y2], [x3, y3]]], would create this arc from its corresponding ellipse.
|
||||
Returns:
|
||||
```
|
||||
[[[x0, y0], [x1, y1]], array of 4 points, specifying the two cuts which
|
||||
[[x2, y2], [x3, y3]]], would create this arc from its corresponding ellipse.
|
||||
```
|
||||
'''
|
||||
a_ranges = self._angles_to_parameters()
|
||||
|
||||
|
|
@ -356,8 +371,9 @@ class Arc(Shape):
|
|||
|
||||
def _angles_to_parameters(self) -> numpy.ndarray:
|
||||
'''
|
||||
:return: "Eccentric anomaly" parameter ranges for the inner and outer edges, in the form
|
||||
[[a_min_inner, a_max_inner], [a_min_outer, a_max_outer]]
|
||||
Returns:
|
||||
"Eccentric anomaly" parameter ranges for the inner and outer edges, in the form
|
||||
`[[a_min_inner, a_max_inner], [a_min_outer, a_max_outer]]`
|
||||
'''
|
||||
a = []
|
||||
for sgn in (-1, +1):
|
||||
|
|
|
|||
|
|
@ -17,16 +17,19 @@ class Circle(Shape):
|
|||
"""
|
||||
__slots__ = ('_radius', 'poly_num_points', 'poly_max_arclen')
|
||||
_radius: float
|
||||
""" Circle radius """
|
||||
|
||||
poly_num_points: int
|
||||
""" Sets the default number of points for `.polygonize()` """
|
||||
|
||||
poly_max_arclen: float
|
||||
""" Sets the default max segement length for `.polygonize()` """
|
||||
|
||||
# radius property
|
||||
@property
|
||||
def radius(self) -> float:
|
||||
"""
|
||||
Circle's radius (float, >= 0)
|
||||
|
||||
:return: radius
|
||||
"""
|
||||
return self._radius
|
||||
|
||||
|
|
|
|||
|
|
@ -20,17 +20,22 @@ class Ellipse(Shape):
|
|||
__slots__ = ('_radii', '_rotation',
|
||||
'poly_num_points', 'poly_max_arclen')
|
||||
_radii: numpy.ndarray
|
||||
""" Ellipse radii """
|
||||
|
||||
_rotation: float
|
||||
""" Angle from x-axis to first radius (ccw, radians) """
|
||||
|
||||
poly_num_points: int
|
||||
""" Sets the default number of points for `.polygonize()` """
|
||||
|
||||
poly_max_arclen: float
|
||||
""" Sets the default max segement length for `.polygonize()` """
|
||||
|
||||
# radius properties
|
||||
@property
|
||||
def radii(self) -> numpy.ndarray:
|
||||
"""
|
||||
Return the radii [rx, ry]
|
||||
|
||||
:return: [rx, ry]
|
||||
Return the radii `[rx, ry]`
|
||||
"""
|
||||
return self._radii
|
||||
|
||||
|
|
@ -70,7 +75,8 @@ class Ellipse(Shape):
|
|||
Rotation of rx from the x axis. Uses the interval [0, pi) in radians (counterclockwise
|
||||
is positive)
|
||||
|
||||
:return: counterclockwise rotation in radians
|
||||
Returns:
|
||||
counterclockwise rotation in radians
|
||||
"""
|
||||
return self._rotation
|
||||
|
||||
|
|
|
|||
|
|
@ -37,8 +37,6 @@ class Path(Shape):
|
|||
def width(self) -> float:
|
||||
"""
|
||||
Path width (float, >= 0)
|
||||
|
||||
:return: width
|
||||
"""
|
||||
return self._width
|
||||
|
||||
|
|
@ -55,8 +53,6 @@ class Path(Shape):
|
|||
def cap(self) -> 'Path.Cap':
|
||||
"""
|
||||
Path end-cap
|
||||
|
||||
:return: Path.Cap enum
|
||||
"""
|
||||
return self._cap
|
||||
|
||||
|
|
@ -74,9 +70,10 @@ class Path(Shape):
|
|||
@property
|
||||
def cap_extensions(self) -> numpy.ndarray or None:
|
||||
"""
|
||||
Path end-cap extensionf
|
||||
Path end-cap extension
|
||||
|
||||
:return: 2-element ndarray or None
|
||||
Returns:
|
||||
2-element ndarray or `None`
|
||||
"""
|
||||
return self._cap_extensions
|
||||
|
||||
|
|
@ -96,9 +93,7 @@ class Path(Shape):
|
|||
@property
|
||||
def vertices(self) -> numpy.ndarray:
|
||||
"""
|
||||
Vertices of the path (Nx2 ndarray: [[x0, y0], [x1, y1], ...]
|
||||
|
||||
:return: vertices
|
||||
Vertices of the path (Nx2 ndarray: `[[x0, y0], [x1, y1], ...]`)
|
||||
"""
|
||||
return self._vertices
|
||||
|
||||
|
|
@ -194,22 +189,25 @@ class Path(Shape):
|
|||
Build a path by specifying the turn angles and travel distances
|
||||
rather than setting the distances directly.
|
||||
|
||||
:param travel_pairs: A list of (angle, distance) pairs that define
|
||||
the path. Angles are counterclockwise, in radians, and are relative
|
||||
to the previous segment's direction (the initial angle is relative
|
||||
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,
|
||||
mirrored=(True, False) results in a reflection across the x-axis,
|
||||
multiplying the path's y-coordinates by -1. Default (False, False)
|
||||
:param layer: Layer, default 0
|
||||
:param dose: Dose, default 1.0
|
||||
:return: The resulting Path object
|
||||
Args:
|
||||
travel_pairs: A list of (angle, distance) pairs that define
|
||||
the path. Angles are counterclockwise, in radians, and are relative
|
||||
to the previous segment's direction (the initial angle is relative
|
||||
to the +x axis).
|
||||
width: Path width, default `0`
|
||||
cap: End-cap type, default `Path.Cap.Flush` (no end-cap)
|
||||
cap_extensions: End-cap extension distances, when using `Path.Cap.CustomSquare`.
|
||||
Default `(0, 0)` or `None`, depending on cap type
|
||||
offset: Offset, default `(0, 0)`
|
||||
rotation: Rotation counterclockwise, in radians. Default `0`
|
||||
mirrored: Whether to mirror across the x or y axes. For example,
|
||||
`mirrored=(True, False)` results in a reflection across the x-axis,
|
||||
multiplying the path's y-coordinates by -1. Default `(False, False)`
|
||||
layer: Layer, default `0`
|
||||
dose: Dose, default `1.0`
|
||||
|
||||
Returns:
|
||||
The resulting Path object
|
||||
"""
|
||||
#TODO: needs testing
|
||||
direction = numpy.array([1, 0])
|
||||
|
|
@ -359,7 +357,8 @@ class Path(Shape):
|
|||
"""
|
||||
Removes duplicate, co-linear and otherwise redundant vertices.
|
||||
|
||||
:returns: self
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
self.remove_colinear_vertices()
|
||||
return self
|
||||
|
|
@ -368,7 +367,8 @@ class Path(Shape):
|
|||
'''
|
||||
Removes all consecutive duplicate (repeated) vertices.
|
||||
|
||||
:returns: self
|
||||
Returns:
|
||||
self
|
||||
'''
|
||||
self.vertices = remove_duplicate_vertices(self.vertices, closed_path=False)
|
||||
return self
|
||||
|
|
@ -377,7 +377,8 @@ class Path(Shape):
|
|||
'''
|
||||
Removes consecutive co-linear vertices.
|
||||
|
||||
:returns: self
|
||||
Returns:
|
||||
self
|
||||
'''
|
||||
self.vertices = remove_colinear_vertices(self.vertices, closed_path=False)
|
||||
return self
|
||||
|
|
|
|||
|
|
@ -16,18 +16,17 @@ class Polygon(Shape):
|
|||
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.
|
||||
"""
|
||||
__slots__ = ('_vertices',)
|
||||
_vertices: numpy.ndarray
|
||||
""" Nx2 ndarray of vertices `[[x0, y0], [x1, y1], ...]` """
|
||||
|
||||
# vertices property
|
||||
@property
|
||||
def vertices(self) -> numpy.ndarray:
|
||||
"""
|
||||
Vertices of the polygon (Nx2 ndarray: [[x0, y0], [x1, y1], ...]
|
||||
|
||||
:return: vertices
|
||||
Vertices of the polygon (Nx2 ndarray: `[[x0, y0], [x1, y1], ...]`)
|
||||
"""
|
||||
return self._vertices
|
||||
|
||||
|
|
@ -107,12 +106,15 @@ class Polygon(Shape):
|
|||
"""
|
||||
Draw a square given side_length, centered on the origin.
|
||||
|
||||
:param side_length: Length of one side
|
||||
: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
|
||||
Args:
|
||||
side_length: Length of one side
|
||||
rotation: Rotation counterclockwise, in radians
|
||||
offset: Offset, default `(0, 0)`
|
||||
layer: Layer, default `0`
|
||||
dose: Dose, default `1.0`
|
||||
|
||||
Returns:
|
||||
A Polygon object containing the requested square
|
||||
"""
|
||||
norm_square = numpy.array([[-1, -1],
|
||||
[-1, +1],
|
||||
|
|
@ -134,13 +136,16 @@ class Polygon(Shape):
|
|||
"""
|
||||
Draw a rectangle with side lengths lx and ly, centered on the origin.
|
||||
|
||||
:param lx: Length along x (before rotation)
|
||||
:param ly: Length along y (before rotation)
|
||||
: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 rectangle
|
||||
Args:
|
||||
lx: Length along x (before rotation)
|
||||
ly: Length along y (before rotation)
|
||||
rotation: Rotation counterclockwise, in radians
|
||||
offset: Offset, default `(0, 0)`
|
||||
layer: Layer, default `0`
|
||||
dose: Dose, default `1.0`
|
||||
|
||||
Returns:
|
||||
A Polygon object containing the requested rectangle
|
||||
"""
|
||||
vertices = 0.5 * numpy.array([[-lx, -ly],
|
||||
[-lx, +ly],
|
||||
|
|
@ -168,17 +173,20 @@ class Polygon(Shape):
|
|||
Must provide 2 of (xmin, xctr, xmax, lx),
|
||||
and 2 of (ymin, yctr, ymax, ly).
|
||||
|
||||
:param xmin: Minimum x coordinate
|
||||
:param xctr: Center x coordinate
|
||||
:param xmax: Maximum x coordinate
|
||||
:param lx: Length along x direction
|
||||
:param ymin: Minimum y coordinate
|
||||
:param yctr: Center y coordinate
|
||||
:param ymax: Maximum y coordinate
|
||||
:param ly: Length along y direction
|
||||
:param layer: Layer, default 0
|
||||
:param dose: Dose, default 1.0
|
||||
:return: A Polygon object containing the requested rectangle
|
||||
Args:
|
||||
xmin: Minimum x coordinate
|
||||
xctr: Center x coordinate
|
||||
xmax: Maximum x coordinate
|
||||
lx: Length along x direction
|
||||
ymin: Minimum y coordinate
|
||||
yctr: Center y coordinate
|
||||
ymax: Maximum y coordinate
|
||||
ly: Length along y direction
|
||||
layer: Layer, default `0`
|
||||
dose: Dose, default `1.0`
|
||||
|
||||
Returns:
|
||||
A Polygon object containing the requested rectangle
|
||||
"""
|
||||
if lx is None:
|
||||
if xctr is None:
|
||||
|
|
@ -278,7 +286,8 @@ class Polygon(Shape):
|
|||
"""
|
||||
Removes duplicate, co-linear and otherwise redundant vertices.
|
||||
|
||||
:returns: self
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
self.remove_colinear_vertices()
|
||||
return self
|
||||
|
|
@ -287,7 +296,8 @@ class Polygon(Shape):
|
|||
'''
|
||||
Removes all consecutive duplicate (repeated) vertices.
|
||||
|
||||
:returns: self
|
||||
Returns:
|
||||
self
|
||||
'''
|
||||
self.vertices = remove_duplicate_vertices(self.vertices, closed_path=True)
|
||||
return self
|
||||
|
|
@ -296,7 +306,8 @@ class Polygon(Shape):
|
|||
'''
|
||||
Removes consecutive co-linear vertices.
|
||||
|
||||
:returns: self
|
||||
Returns:
|
||||
self
|
||||
'''
|
||||
self.vertices = remove_colinear_vertices(self.vertices, closed_path=True)
|
||||
return self
|
||||
|
|
|
|||
|
|
@ -26,12 +26,20 @@ class Shape(metaclass=ABCMeta):
|
|||
"""
|
||||
__slots__ = ('_offset', '_layer', '_dose', 'identifier', 'locked')
|
||||
|
||||
_offset: numpy.ndarray # [x_offset, y_offset]
|
||||
_layer: int or Tuple # Layer (integer >= 0 or tuple)
|
||||
_dose: float # Dose
|
||||
identifier: Tuple # An arbitrary identifier for the shape,
|
||||
# usually empty but used by Pattern.flatten()
|
||||
locked: bool # If True, any changes to the shape will raise a PatternLockedError
|
||||
_offset: numpy.ndarray
|
||||
""" `[x_offset, y_offset]` """
|
||||
|
||||
_layer: int or Tuple
|
||||
""" Layer (integer >= 0 or tuple) """
|
||||
|
||||
_dose: float
|
||||
""" Dose """
|
||||
|
||||
identifier: Tuple
|
||||
""" An arbitrary identifier for the shape, usually empty but used by `Pattern.flatten()` """
|
||||
|
||||
locked: bool
|
||||
""" If `True`, any changes to the shape will raise a `PatternLockedError` """
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if self.locked and name != 'locked':
|
||||
|
|
@ -51,31 +59,35 @@ class Shape(metaclass=ABCMeta):
|
|||
"""
|
||||
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
|
||||
Args:
|
||||
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.
|
||||
max_arclen: Maximum arclength which can be approximated by a single line
|
||||
segment. Optional, defaults to shapes' internal defaults.
|
||||
|
||||
Returns:
|
||||
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]]
|
||||
Returns `[[x_min, y_min], [x_max, y_max]]` which specify a minimal bounding box for the shape.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def rotate(self, theta: float) -> 'Shape':
|
||||
"""
|
||||
Rotate the shape around its center (0, 0), ignoring its offset.
|
||||
Rotate the shape around its origin (0, 0), ignoring its offset.
|
||||
|
||||
:param theta: Angle to rotate by (counterclockwise, radians)
|
||||
:return: self
|
||||
Args:
|
||||
theta: Angle to rotate by (counterclockwise, radians)
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
pass
|
||||
|
||||
|
|
@ -84,8 +96,12 @@ class Shape(metaclass=ABCMeta):
|
|||
"""
|
||||
Mirror the shape across an axis.
|
||||
|
||||
:param axis: Axis to mirror across.
|
||||
:return: self
|
||||
Args:
|
||||
axis: Axis to mirror across.
|
||||
(0: mirror across x axis, 1: mirror across y axis)
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
pass
|
||||
|
||||
|
|
@ -94,8 +110,11 @@ class Shape(metaclass=ABCMeta):
|
|||
"""
|
||||
Scale the shape's size (eg. radius, for a circle) by a constant factor.
|
||||
|
||||
:param c: Factor to scale by
|
||||
:return: self
|
||||
Args:
|
||||
c: Factor to scale by
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
pass
|
||||
|
||||
|
|
@ -105,18 +124,21 @@ class Shape(metaclass=ABCMeta):
|
|||
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 the shape;
|
||||
Args:
|
||||
norm_value: This value is used to normalize lengths intrinsic to the shape;
|
||||
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
|
||||
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:
|
||||
intrinsic: A tuple of basic types containing all information about the instance that
|
||||
is not contained in 'extrinsic'. Usually, intrinsic[0] == type(self).
|
||||
extrinsic: ([x_offset, y_offset], scale, rotation, mirror_across_x_axis, dose)
|
||||
constructor: A callable (no arguments) which returns an instance of type(self) with
|
||||
internal state equivalent to 'intrinsic'.
|
||||
|
||||
Returns:
|
||||
The returned information takes the form of a 3-element tuple,
|
||||
`(intrinsic, extrinsic, constructor)`. These are further broken down as:
|
||||
`intrinsic`: A tuple of basic types containing all information about the instance that
|
||||
is not contained in 'extrinsic'. Usually, `intrinsic[0] == type(self)`.
|
||||
`extrinsic`: `([x_offset, y_offset], scale, rotation, mirror_across_x_axis, dose)`
|
||||
`constructor`: A callable (no arguments) which returns an instance of `type(self)` with
|
||||
internal state equivalent to `intrinsic`.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
|
@ -126,8 +148,6 @@ class Shape(metaclass=ABCMeta):
|
|||
def offset(self) -> numpy.ndarray:
|
||||
"""
|
||||
[x, y] offset
|
||||
|
||||
:return: [x_offset, y_offset]
|
||||
"""
|
||||
return self._offset
|
||||
|
||||
|
|
@ -145,8 +165,6 @@ class Shape(metaclass=ABCMeta):
|
|||
def layer(self) -> int or Tuple[int]:
|
||||
"""
|
||||
Layer number (int or tuple of ints)
|
||||
|
||||
:return: Layer
|
||||
"""
|
||||
return self._layer
|
||||
|
||||
|
|
@ -159,8 +177,6 @@ class Shape(metaclass=ABCMeta):
|
|||
def dose(self) -> float:
|
||||
"""
|
||||
Dose (float >= 0)
|
||||
|
||||
:return: Dose value
|
||||
"""
|
||||
return self._dose
|
||||
|
||||
|
|
@ -177,7 +193,8 @@ class Shape(metaclass=ABCMeta):
|
|||
"""
|
||||
Returns a deep copy of the shape.
|
||||
|
||||
:return: Deep copy of self
|
||||
Returns:
|
||||
copy.deepcopy(self)
|
||||
"""
|
||||
return copy.deepcopy(self)
|
||||
|
||||
|
|
@ -185,8 +202,11 @@ class Shape(metaclass=ABCMeta):
|
|||
"""
|
||||
Translate the shape by the given offset
|
||||
|
||||
:param offset: [x_offset, y,offset]
|
||||
:return: self
|
||||
Args:
|
||||
offset: [x_offset, y,offset]
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
self.offset += offset
|
||||
return self
|
||||
|
|
@ -195,9 +215,12 @@ class Shape(metaclass=ABCMeta):
|
|||
"""
|
||||
Rotate the shape around a point.
|
||||
|
||||
:param pivot: Point (x, y) to rotate around
|
||||
:param rotation: Angle to rotate by (counterclockwise, radians)
|
||||
:return: self
|
||||
Args:
|
||||
pivot: Point (x, y) to rotate around
|
||||
rotation: Angle to rotate by (counterclockwise, radians)
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
pivot = numpy.array(pivot, dtype=float)
|
||||
self.translate(-pivot)
|
||||
|
|
@ -214,14 +237,17 @@ class Shape(metaclass=ABCMeta):
|
|||
Returns a list of polygons with grid-aligned ("Manhattan") edges approximating the shape.
|
||||
|
||||
This function works by
|
||||
1) Converting the shape to polygons using .to_polygons()
|
||||
1) Converting the shape to polygons using `.to_polygons()`
|
||||
2) Approximating each edge with an equivalent Manhattan edge
|
||||
This process results in a reasonable Manhattan representation of the shape, but is
|
||||
imprecise near non-Manhattan or off-grid corners.
|
||||
|
||||
:param grid_x: List of allowed x-coordinates for the Manhattanized polygon edges.
|
||||
:param grid_y: List of allowed y-coordinates for the Manhattanized polygon edges.
|
||||
:return: List of Polygon objects with grid-aligned edges.
|
||||
Args:
|
||||
grid_x: List of allowed x-coordinates for the Manhattanized polygon edges.
|
||||
grid_y: List of allowed y-coordinates for the Manhattanized polygon edges.
|
||||
|
||||
Returns:
|
||||
List of `Polygon` objects with grid-aligned edges.
|
||||
"""
|
||||
from . import Polygon
|
||||
|
||||
|
|
@ -319,7 +345,7 @@ class Shape(metaclass=ABCMeta):
|
|||
Returns a list of polygons with grid-aligned ("Manhattan") edges approximating the shape.
|
||||
|
||||
This function works by
|
||||
1) Converting the shape to polygons using .to_polygons()
|
||||
1) Converting the shape to polygons using `.to_polygons()`
|
||||
2) Accurately rasterizing each polygon on a grid,
|
||||
where the edges of each grid cell correspond to the allowed coordinates
|
||||
3) Thresholding the (anti-aliased) rasterized image
|
||||
|
|
@ -328,7 +354,7 @@ class Shape(metaclass=ABCMeta):
|
|||
caveats include:
|
||||
a) If high accuracy is important, perform any polygonization and clipping operations
|
||||
prior to calling this function. This allows you to specify any arguments you may
|
||||
need for .to_polygons(), and also avoids calling .manhattanize() multiple times for
|
||||
need for `.to_polygons()`, and also avoids calling `.manhattanize()` multiple times for
|
||||
the same grid location (which causes inaccuracies in the final representation).
|
||||
b) If the shape is very large or the grid very fine, memory requirements can be reduced
|
||||
by breaking the shape apart into multiple, smaller shapes.
|
||||
|
|
@ -336,19 +362,22 @@ class Shape(metaclass=ABCMeta):
|
|||
equidistant from allowed edge location.
|
||||
|
||||
Implementation notes:
|
||||
i) Rasterization is performed using float_raster, giving a high-precision anti-aliased
|
||||
i) Rasterization is performed using `float_raster`, giving a high-precision anti-aliased
|
||||
rasterized image.
|
||||
ii) To find the exact polygon edges, the thresholded rasterized image is supersampled
|
||||
prior to calling skimage.measure.find_contours(), which uses marching squares
|
||||
to find the contours. This is done because find_contours() performs interpolation,
|
||||
prior to calling `skimage.measure.find_contours()`, which uses marching squares
|
||||
to find the contours. This is done because `find_contours()` performs interpolation,
|
||||
which has to be undone in order to regain the axis-aligned contours. A targetted
|
||||
rewrite of find_contours() for this specific application, or use of a different
|
||||
rewrite of `find_contours()` for this specific application, or use of a different
|
||||
boundary tracing method could remove this requirement, but for now this seems to
|
||||
be the most performant approach.
|
||||
|
||||
:param grid_x: List of allowed x-coordinates for the Manhattanized polygon edges.
|
||||
:param grid_y: List of allowed y-coordinates for the Manhattanized polygon edges.
|
||||
:return: List of Polygon objects with grid-aligned edges.
|
||||
Args:
|
||||
grid_x: List of allowed x-coordinates for the Manhattanized polygon edges.
|
||||
grid_y: List of allowed y-coordinates for the Manhattanized polygon edges.
|
||||
|
||||
Returns:
|
||||
List of `Polygon` objects with grid-aligned edges.
|
||||
"""
|
||||
from . import Polygon
|
||||
import skimage.measure
|
||||
|
|
@ -403,9 +432,10 @@ class Shape(metaclass=ABCMeta):
|
|||
|
||||
def lock(self) -> 'Shape':
|
||||
"""
|
||||
Lock the Shape
|
||||
Lock the Shape, disallowing further changes
|
||||
|
||||
:return: self
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
object.__setattr__(self, 'locked', True)
|
||||
return self
|
||||
|
|
@ -414,7 +444,8 @@ class Shape(metaclass=ABCMeta):
|
|||
"""
|
||||
Unlock the Shape
|
||||
|
||||
:return: self
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
object.__setattr__(self, 'locked', False)
|
||||
return self
|
||||
|
|
|
|||
|
|
@ -173,12 +173,15 @@ def get_char_as_polygons(font_path: str,
|
|||
|
||||
The output is normalized so that the font size is 1 unit.
|
||||
|
||||
:param font_path: File path specifying a font loadable by freetype
|
||||
:param char: Character to convert to polygons
|
||||
:param resolution: Internal resolution setting (used for freetype
|
||||
Face.set_font_size(resolution)). Modify at your own peril!
|
||||
:return: List of polygons [[[x0, y0], [x1, y1], ...], ...] and 'advance' distance (distance
|
||||
from the start of this glyph to the start of the next one)
|
||||
Args:
|
||||
font_path: File path specifying a font loadable by freetype
|
||||
char: Character to convert to polygons
|
||||
resolution: Internal resolution setting (used for freetype
|
||||
`Face.set_font_size(resolution))`. Modify at your own peril!
|
||||
|
||||
Returns:
|
||||
List of polygons `[[[x0, y0], [x1, y1], ...], ...]` and
|
||||
'advance' distance (distance from the start of this glyph to the start of the next one)
|
||||
"""
|
||||
if len(char) != 1:
|
||||
raise Exception('get_char_as_polygons called with non-char')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue