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
@ -6,21 +6,24 @@
|
|||||||
with some vectorized element types (eg. circles, not just polygons), better support for
|
with some vectorized element types (eg. circles, not just polygons), better support for
|
||||||
E-beam doses, and the ability to output to multiple formats.
|
E-beam doses, and the ability to output to multiple formats.
|
||||||
|
|
||||||
Pattern is a basic object containing a 2D lithography mask, composed of a list of Shape
|
`Pattern` is a basic object containing a 2D lithography mask, composed of a list of `Shape`
|
||||||
objects and a list of SubPattern objects.
|
objects, a list of `Label` objects, and a list of references to other `Patterns` (using
|
||||||
|
`SubPattern` and `GridRepetition`).
|
||||||
|
|
||||||
SubPattern provides basic support for nesting Pattern objects within each other, by adding
|
`SubPattern` provides basic support for nesting `Pattern` objects within each other, by adding
|
||||||
offset, rotation, scaling, and other such properties to a Pattern reference.
|
offset, rotation, scaling, and other such properties to a Pattern reference.
|
||||||
|
|
||||||
|
`GridRepetition` provides support for nesting regular arrays of `Pattern` objects.
|
||||||
|
|
||||||
Note that the methods for these classes try to avoid copying wherever possible, so unless
|
Note that the methods for these classes try to avoid copying wherever possible, so unless
|
||||||
otherwise noted, assume that arguments are stored by-reference.
|
otherwise noted, assume that arguments are stored by-reference.
|
||||||
|
|
||||||
|
|
||||||
Dependencies:
|
Dependencies:
|
||||||
- numpy
|
- `numpy`
|
||||||
- matplotlib [Pattern.visualize(...)]
|
- `matplotlib` [Pattern.visualize(...)]
|
||||||
- python-gdsii [masque.file.gdsii]
|
- `python-gdsii` [masque.file.gdsii]
|
||||||
- svgwrite [masque.file.svg]
|
- `svgwrite` [masque.file.svg]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pathlib
|
import pathlib
|
||||||
|
@ -49,36 +49,37 @@ def write(patterns: Pattern or List[Pattern],
|
|||||||
modify_originals: bool = False,
|
modify_originals: bool = False,
|
||||||
disambiguate_func: Callable[[List[Pattern]], None] = None):
|
disambiguate_func: Callable[[List[Pattern]], None] = None):
|
||||||
"""
|
"""
|
||||||
Write a Pattern or list of patterns to a GDSII file, by first calling
|
Write a `Pattern` or list of patterns to a GDSII file, by first calling
|
||||||
.polygonize() to change the shapes into polygons, and then writing patterns
|
`.polygonize()` to change the shapes into polygons, and then writing patterns
|
||||||
as GDSII structures, polygons as boundary elements, and subpatterns as structure
|
as GDSII structures, polygons as boundary elements, and subpatterns as structure
|
||||||
references (sref).
|
references (sref).
|
||||||
|
|
||||||
For each shape,
|
For each shape,
|
||||||
layer is chosen to be equal to shape.layer if it is an int,
|
layer is chosen to be equal to `shape.layer` if it is an int,
|
||||||
or shape.layer[0] if it is a tuple
|
or `shape.layer[0]` if it is a tuple
|
||||||
datatype is chosen to be shape.layer[1] if available,
|
datatype is chosen to be `shape.layer[1]` if available,
|
||||||
otherwise 0
|
otherwise `0`
|
||||||
|
|
||||||
It is often a good idea to run pattern.subpatternize() prior to calling this function,
|
It is often a good idea to run `pattern.subpatternize()` prior to calling this function,
|
||||||
especially if calling .polygonize() will result in very many vertices.
|
especially if calling `.polygonize()` will result in very many vertices.
|
||||||
|
|
||||||
If you want pattern polygonized with non-default arguments, just call pattern.polygonize()
|
If you want pattern polygonized with non-default arguments, just call `pattern.polygonize()`
|
||||||
prior to calling this function.
|
prior to calling this function.
|
||||||
|
|
||||||
:param patterns: A Pattern or list of patterns to write to file.
|
Args:
|
||||||
:param file: Filename or stream object to write to.
|
patterns: A Pattern or list of patterns to write to file.
|
||||||
:param meters_per_unit: Written into the GDSII file, meters per (database) length unit.
|
file: Filename or stream object to write to.
|
||||||
|
meters_per_unit: Written into the GDSII file, meters per (database) length unit.
|
||||||
All distances are assumed to be an integer multiple of this unit, and are stored as such.
|
All distances are assumed to be an integer multiple of this unit, and are stored as such.
|
||||||
:param logical_units_per_unit: Written into the GDSII file. Allows the GDSII to specify a
|
logical_units_per_unit: Written into the GDSII file. Allows the GDSII to specify a
|
||||||
"logical" unit which is different from the "database" unit, for display purposes.
|
"logical" unit which is different from the "database" unit, for display purposes.
|
||||||
Default 1.
|
Default `1`.
|
||||||
:param library_name: Library name written into the GDSII file.
|
library_name: Library name written into the GDSII file.
|
||||||
Default 'masque-gdsii-write'.
|
Default 'masque-gdsii-write'.
|
||||||
:param modify_originals: If True, the original pattern is modified as part of the writing
|
modify_originals: If `True`, the original pattern is modified as part of the writing
|
||||||
process. Otherwise, a copy is made and deepunlock()-ed.
|
process. Otherwise, a copy is made and `deepunlock()`-ed.
|
||||||
Default False.
|
Default `False`.
|
||||||
:param disambiguate_func: Function which takes a list of patterns and alters them
|
disambiguate_func: Function which takes a list of patterns and alters them
|
||||||
to make their names valid and unique. Default is `disambiguate_pattern_names`, which
|
to make their names valid and unique. Default is `disambiguate_pattern_names`, which
|
||||||
attempts to adhere to the GDSII standard as well as possible.
|
attempts to adhere to the GDSII standard as well as possible.
|
||||||
WARNING: No additional error checking is performed on the results.
|
WARNING: No additional error checking is performed on the results.
|
||||||
@ -124,9 +125,15 @@ def writefile(patterns: List[Pattern] or Pattern,
|
|||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Wrapper for gdsii.write() that takes a filename or path instead of a stream.
|
Wrapper for `gdsii.write()` that takes a filename or path instead of a stream.
|
||||||
|
|
||||||
Will automatically compress the file if it has a .gz suffix.
|
Will automatically compress the file if it has a .gz suffix.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
patterns: `Pattern` or list of patterns to save
|
||||||
|
filename: Filename to save to.
|
||||||
|
*args: passed to `gdsii.write`
|
||||||
|
**kwargs: passed to `gdsii.write`
|
||||||
"""
|
"""
|
||||||
path = pathlib.Path(filename)
|
path = pathlib.Path(filename)
|
||||||
if path.suffix == '.gz':
|
if path.suffix == '.gz':
|
||||||
@ -153,8 +160,11 @@ def dose2dtype(patterns: List[Pattern],
|
|||||||
|
|
||||||
Note that this function modifies the input Pattern(s).
|
Note that this function modifies the input Pattern(s).
|
||||||
|
|
||||||
:param patterns: A Pattern or list of patterns to write to file. Modified by this function.
|
Args:
|
||||||
:returns: (patterns, dose_list)
|
patterns: A `Pattern` or list of patterns to write to file. Modified by this function.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(patterns, dose_list)
|
||||||
patterns: modified input patterns
|
patterns: modified input patterns
|
||||||
dose_list: A list of doses, providing a mapping between datatype (int, list index)
|
dose_list: A list of doses, providing a mapping between datatype (int, list index)
|
||||||
and dose (float, list entry).
|
and dose (float, list entry).
|
||||||
@ -221,9 +231,14 @@ def readfile(filename: str or pathlib.Path,
|
|||||||
**kwargs,
|
**kwargs,
|
||||||
) -> (Dict[str, Pattern], Dict[str, Any]):
|
) -> (Dict[str, Pattern], Dict[str, Any]):
|
||||||
"""
|
"""
|
||||||
Wrapper for gdsii.read() that takes a filename or path instead of a stream.
|
Wrapper for `gdsii.read()` that takes a filename or path instead of a stream.
|
||||||
|
|
||||||
Tries to autodetermine file type based on suffixes
|
Will automatically decompress files with a .gz suffix.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename: Filename to save to.
|
||||||
|
*args: passed to `gdsii.read`
|
||||||
|
**kwargs: passed to `gdsii.read`
|
||||||
"""
|
"""
|
||||||
path = pathlib.Path(filename)
|
path = pathlib.Path(filename)
|
||||||
if path.suffix == '.gz':
|
if path.suffix == '.gz':
|
||||||
@ -251,14 +266,18 @@ def read(stream: io.BufferedIOBase,
|
|||||||
'logical_units_per_unit': number of "logical" units displayed by layout tools (typically microns)
|
'logical_units_per_unit': number of "logical" units displayed by layout tools (typically microns)
|
||||||
per database unit
|
per database unit
|
||||||
|
|
||||||
:param filename: Filename specifying a GDSII file to read from.
|
Args:
|
||||||
:param use_dtype_as_dose: If false, set each polygon's layer to (gds_layer, gds_datatype).
|
filename: Filename specifying a GDSII file to read from.
|
||||||
If true, set the layer to gds_layer and the dose to gds_datatype.
|
use_dtype_as_dose: If `False`, set each polygon's layer to `(gds_layer, gds_datatype)`.
|
||||||
Default False.
|
If `True`, set the layer to `gds_layer` and the dose to `gds_datatype`.
|
||||||
:param clean_vertices: If true, remove any redundant vertices when loading polygons.
|
Default `False`.
|
||||||
|
clean_vertices: If `True`, remove any redundant vertices when loading polygons.
|
||||||
The cleaning process removes any polygons with zero area or <3 vertices.
|
The cleaning process removes any polygons with zero area or <3 vertices.
|
||||||
Default True.
|
Default `True`.
|
||||||
:return: Tuple: (Dict of pattern_name:Patterns generated from GDSII structures, Dict of GDSII library info)
|
|
||||||
|
Returns:
|
||||||
|
- Dict of pattern_name:Patterns generated from GDSII structures
|
||||||
|
- Dict of GDSII library info
|
||||||
"""
|
"""
|
||||||
|
|
||||||
lib = gdsii.library.Library.load(stream)
|
lib = gdsii.library.Library.load(stream)
|
||||||
@ -353,6 +372,7 @@ def read(stream: io.BufferedIOBase,
|
|||||||
|
|
||||||
|
|
||||||
def _mlayer2gds(mlayer):
|
def _mlayer2gds(mlayer):
|
||||||
|
""" Helper to turn a layer tuple-or-int into a layer and datatype"""
|
||||||
if is_scalar(mlayer):
|
if is_scalar(mlayer):
|
||||||
layer = mlayer
|
layer = mlayer
|
||||||
data_type = 0
|
data_type = 0
|
||||||
@ -366,12 +386,15 @@ def _mlayer2gds(mlayer):
|
|||||||
|
|
||||||
|
|
||||||
def _sref_to_subpat(element: gdsii.elements.SRef) -> SubPattern:
|
def _sref_to_subpat(element: gdsii.elements.SRef) -> SubPattern:
|
||||||
# Helper function to create a SubPattern from an SREF. Sets subpat.pattern to None
|
"""
|
||||||
# and sets the instance .identifier to the struct_name.
|
Helper function to create a SubPattern from an SREF. Sets subpat.pattern to None
|
||||||
#
|
and sets the instance .identifier to the struct_name.
|
||||||
# BUG: "Absolute" means not affected by parent elements.
|
|
||||||
# That's not currently supported by masque at all, so need to either tag it and
|
BUG:
|
||||||
# undo the parent transformations, or implement it in masque.
|
"Absolute" means not affected by parent elements.
|
||||||
|
That's not currently supported by masque at all, so need to either tag it and
|
||||||
|
undo the parent transformations, or implement it in masque.
|
||||||
|
"""
|
||||||
subpat = SubPattern(pattern=None, offset=element.xy)
|
subpat = SubPattern(pattern=None, offset=element.xy)
|
||||||
subpat.identifier = element.struct_name
|
subpat.identifier = element.struct_name
|
||||||
if element.strans is not None:
|
if element.strans is not None:
|
||||||
@ -394,13 +417,15 @@ def _sref_to_subpat(element: gdsii.elements.SRef) -> SubPattern:
|
|||||||
|
|
||||||
|
|
||||||
def _aref_to_gridrep(element: gdsii.elements.ARef) -> GridRepetition:
|
def _aref_to_gridrep(element: gdsii.elements.ARef) -> GridRepetition:
|
||||||
# Helper function to create a GridRepetition from an AREF. Sets gridrep.pattern to None
|
"""
|
||||||
# and sets the instance .identifier to the struct_name.
|
Helper function to create a GridRepetition from an AREF. Sets gridrep.pattern to None
|
||||||
#
|
and sets the instance .identifier to the struct_name.
|
||||||
# BUG: "Absolute" means not affected by parent elements.
|
|
||||||
# That's not currently supported by masque at all, so need to either tag it and
|
|
||||||
# undo the parent transformations, or implement it in masque.i
|
|
||||||
|
|
||||||
|
BUG:
|
||||||
|
"Absolute" means not affected by parent elements.
|
||||||
|
That's not currently supported by masque at all, so need to either tag it and
|
||||||
|
undo the parent transformations, or implement it in masque.
|
||||||
|
"""
|
||||||
rotation = 0
|
rotation = 0
|
||||||
offset = numpy.array(element.xy[0])
|
offset = numpy.array(element.xy[0])
|
||||||
scale = 1
|
scale = 1
|
||||||
|
@ -23,20 +23,21 @@ def writefile(pattern: Pattern,
|
|||||||
|
|
||||||
Note that this function modifies the Pattern.
|
Note that this function modifies the Pattern.
|
||||||
|
|
||||||
If custom_attributes is True, non-standard pattern_layer and pattern_dose attributes
|
If `custom_attributes` is `True`, non-standard `pattern_layer` and `pattern_dose` attributes
|
||||||
are written to the relevant elements.
|
are written to the relevant elements.
|
||||||
|
|
||||||
It is often a good idea to run pattern.subpatternize() on pattern prior to
|
It is often a good idea to run `pattern.subpatternize()` on pattern prior to
|
||||||
calling this function, especially if calling .polygonize() will result in very
|
calling this function, especially if calling `.polygonize()` will result in very
|
||||||
many vertices.
|
many vertices.
|
||||||
|
|
||||||
If you want pattern polygonized with non-default arguments, just call pattern.polygonize()
|
If you want pattern polygonized with non-default arguments, just call `pattern.polygonize()`
|
||||||
prior to calling this function.
|
prior to calling this function.
|
||||||
|
|
||||||
:param pattern: Pattern to write to file. Modified by this function.
|
Args:
|
||||||
:param filename: Filename to write to.
|
pattern: Pattern to write to file. Modified by this function.
|
||||||
:param custom_attributes: Whether to write non-standard pattern_layer and
|
filename: Filename to write to.
|
||||||
pattern_dose attributes to the SVG elements.
|
custom_attributes: Whether to write non-standard `pattern_layer` and
|
||||||
|
`pattern_dose` attributes to the SVG elements.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Polygonize pattern
|
# Polygonize pattern
|
||||||
@ -85,18 +86,19 @@ def writefile(pattern: Pattern,
|
|||||||
|
|
||||||
def writefile_inverted(pattern: Pattern, filename: str):
|
def writefile_inverted(pattern: Pattern, filename: str):
|
||||||
"""
|
"""
|
||||||
Write an inverted Pattern to an SVG file, by first calling .polygonize() and
|
Write an inverted Pattern to an SVG file, by first calling `.polygonize()` and
|
||||||
.flatten() on it to change the shapes into polygons, then drawing a bounding
|
`.flatten()` on it to change the shapes into polygons, then drawing a bounding
|
||||||
box and drawing the polygons with reverse vertex order inside it, all within
|
box and drawing the polygons with reverse vertex order inside it, all within
|
||||||
one <path> element.
|
one `<path>` element.
|
||||||
|
|
||||||
Note that this function modifies the Pattern.
|
Note that this function modifies the Pattern.
|
||||||
|
|
||||||
If you want pattern polygonized with non-default arguments, just call pattern.polygonize()
|
If you want pattern polygonized with non-default arguments, just call `pattern.polygonize()`
|
||||||
prior to calling this function.
|
prior to calling this function.
|
||||||
|
|
||||||
:param pattern: Pattern to write to file. Modified by this function.
|
Args:
|
||||||
:param filename: Filename to write to.
|
pattern: Pattern to write to file. Modified by this function.
|
||||||
|
filename: Filename to write to.
|
||||||
"""
|
"""
|
||||||
# Polygonize and flatten pattern
|
# Polygonize and flatten pattern
|
||||||
pattern.polygonize().flatten()
|
pattern.polygonize().flatten()
|
||||||
@ -129,8 +131,11 @@ def poly2path(vertices: numpy.ndarray) -> str:
|
|||||||
"""
|
"""
|
||||||
Create an SVG path string from an Nx2 list of vertices.
|
Create an SVG path string from an Nx2 list of vertices.
|
||||||
|
|
||||||
:param vertices: Nx2 array of vertices.
|
Args:
|
||||||
:return: SVG path-string.
|
vertices: Nx2 array of vertices.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
SVG path-string.
|
||||||
"""
|
"""
|
||||||
commands = 'M{:g},{:g} '.format(vertices[0][0], vertices[0][1])
|
commands = 'M{:g},{:g} '.format(vertices[0][0], vertices[0][1])
|
||||||
for vertex in vertices[1:]:
|
for vertex in vertices[1:]:
|
||||||
|
@ -12,11 +12,14 @@ __author__ = 'Jan Petykiewicz'
|
|||||||
|
|
||||||
def mangle_name(pattern: Pattern, dose_multiplier: float=1.0) -> str:
|
def mangle_name(pattern: Pattern, dose_multiplier: float=1.0) -> str:
|
||||||
"""
|
"""
|
||||||
Create a name using pattern.name, id(pattern), and the dose multiplier.
|
Create a name using `pattern.name`, `id(pattern)`, and the dose multiplier.
|
||||||
|
|
||||||
:param pattern: Pattern whose name we want to mangle.
|
Args:
|
||||||
:param dose_multiplier: Dose multiplier to mangle with.
|
pattern: Pattern whose name we want to mangle.
|
||||||
:return: Mangled name.
|
dose_multiplier: Dose multiplier to mangle with.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Mangled name.
|
||||||
"""
|
"""
|
||||||
expression = re.compile('[^A-Za-z0-9_\?\$]')
|
expression = re.compile('[^A-Za-z0-9_\?\$]')
|
||||||
full_name = '{}_{}_{}'.format(pattern.name, dose_multiplier, id(pattern))
|
full_name = '{}_{}_{}'.format(pattern.name, dose_multiplier, id(pattern))
|
||||||
@ -26,11 +29,14 @@ def mangle_name(pattern: Pattern, dose_multiplier: float=1.0) -> str:
|
|||||||
|
|
||||||
def make_dose_table(patterns: List[Pattern], dose_multiplier: float=1.0) -> Set[Tuple[int, float]]:
|
def make_dose_table(patterns: List[Pattern], dose_multiplier: float=1.0) -> Set[Tuple[int, float]]:
|
||||||
"""
|
"""
|
||||||
Create a set containing (id(pat), written_dose) for each pattern (including subpatterns)
|
Create a set containing `(id(pat), written_dose)` for each pattern (including subpatterns)
|
||||||
|
|
||||||
:param pattern: Source Patterns.
|
Args:
|
||||||
:param dose_multiplier: Multiplier for all written_dose entries.
|
pattern: Source Patterns.
|
||||||
:return: {(id(subpat.pattern), written_dose), ...}
|
dose_multiplier: Multiplier for all written_dose entries.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
`{(id(subpat.pattern), written_dose), ...}`
|
||||||
"""
|
"""
|
||||||
dose_table = {(id(pattern), dose_multiplier) for pattern in patterns}
|
dose_table = {(id(pattern), dose_multiplier) for pattern in patterns}
|
||||||
for pattern in patterns:
|
for pattern in patterns:
|
||||||
|
@ -15,19 +15,21 @@ class Label:
|
|||||||
A text annotation with a position and layer (but no size; it is not drawn)
|
A text annotation with a position and layer (but no size; it is not drawn)
|
||||||
"""
|
"""
|
||||||
__slots__ = ('_offset', '_layer', '_string', 'identifier', 'locked')
|
__slots__ = ('_offset', '_layer', '_string', 'identifier', 'locked')
|
||||||
# [x_offset, y_offset]
|
|
||||||
_offset: numpy.ndarray
|
_offset: numpy.ndarray
|
||||||
|
""" [x_offset, y_offset] """
|
||||||
|
|
||||||
# Layer (integer >= 0) or 2-Tuple of integers
|
|
||||||
_layer: int or Tuple
|
_layer: int or Tuple
|
||||||
|
""" Layer (integer >= 0, or 2-Tuple of integers) """
|
||||||
|
|
||||||
# Label string
|
|
||||||
_string: str
|
_string: str
|
||||||
|
""" Label string """
|
||||||
|
|
||||||
# Arbitrary identifier tuple
|
|
||||||
identifier: Tuple
|
identifier: Tuple
|
||||||
|
""" Arbitrary identifier tuple, useful for keeping track of history when flattening """
|
||||||
|
|
||||||
locked: bool # If True, any changes to the label will raise a PatternLockedError
|
locked: bool
|
||||||
|
""" If `True`, any changes to the label will raise a `PatternLockedError` """
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
def __setattr__(self, name, value):
|
||||||
if self.locked and name != 'locked':
|
if self.locked and name != 'locked':
|
||||||
@ -40,8 +42,6 @@ class Label:
|
|||||||
def offset(self) -> numpy.ndarray:
|
def offset(self) -> numpy.ndarray:
|
||||||
"""
|
"""
|
||||||
[x, y] offset
|
[x, y] offset
|
||||||
|
|
||||||
:return: [x_offset, y_offset]
|
|
||||||
"""
|
"""
|
||||||
return self._offset
|
return self._offset
|
||||||
|
|
||||||
@ -59,8 +59,6 @@ class Label:
|
|||||||
def layer(self) -> int or Tuple[int]:
|
def layer(self) -> int or Tuple[int]:
|
||||||
"""
|
"""
|
||||||
Layer number (int or tuple of ints)
|
Layer number (int or tuple of ints)
|
||||||
|
|
||||||
:return: Layer
|
|
||||||
"""
|
"""
|
||||||
return self._layer
|
return self._layer
|
||||||
|
|
||||||
@ -73,8 +71,6 @@ class Label:
|
|||||||
def string(self) -> str:
|
def string(self) -> str:
|
||||||
"""
|
"""
|
||||||
Label string (str)
|
Label string (str)
|
||||||
|
|
||||||
:return: string
|
|
||||||
"""
|
"""
|
||||||
return self._string
|
return self._string
|
||||||
|
|
||||||
@ -109,29 +105,33 @@ class Label:
|
|||||||
|
|
||||||
def copy(self) -> 'Label':
|
def copy(self) -> 'Label':
|
||||||
"""
|
"""
|
||||||
Returns a deep copy of the shape.
|
Returns a deep copy of the label.
|
||||||
|
|
||||||
:return: Deep copy of self
|
|
||||||
"""
|
"""
|
||||||
return copy.deepcopy(self)
|
return copy.deepcopy(self)
|
||||||
|
|
||||||
def translate(self, offset: vector2) -> 'Label':
|
def translate(self, offset: vector2) -> 'Label':
|
||||||
"""
|
"""
|
||||||
Translate the shape by the given offset
|
Translate the label by the given offset
|
||||||
|
|
||||||
:param offset: [x_offset, y,offset]
|
Args:
|
||||||
:return: self
|
offset: [x_offset, y,offset]
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
self.offset += offset
|
self.offset += offset
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def rotate_around(self, pivot: vector2, rotation: float) -> 'Label':
|
def rotate_around(self, pivot: vector2, rotation: float) -> 'Label':
|
||||||
"""
|
"""
|
||||||
Rotate the shape around a point.
|
Rotate the label around a point.
|
||||||
|
|
||||||
:param pivot: Point (x, y) to rotate around
|
Args:
|
||||||
:param rotation: Angle to rotate by (counterclockwise, radians)
|
pivot: Point (x, y) to rotate around
|
||||||
:return: self
|
rotation: Angle to rotate by (counterclockwise, radians)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
pivot = numpy.array(pivot, dtype=float)
|
pivot = numpy.array(pivot, dtype=float)
|
||||||
self.translate(-pivot)
|
self.translate(-pivot)
|
||||||
@ -147,24 +147,27 @@ class Label:
|
|||||||
bounds = [self.offset,
|
bounds = [self.offset,
|
||||||
self.offset]
|
self.offset]
|
||||||
|
|
||||||
:return: Bounds [[xmin, xmax], [ymin, ymax]]
|
Returns:
|
||||||
|
Bounds [[xmin, xmax], [ymin, ymax]]
|
||||||
"""
|
"""
|
||||||
return numpy.array([self.offset, self.offset])
|
return numpy.array([self.offset, self.offset])
|
||||||
|
|
||||||
def lock(self) -> 'Label':
|
def lock(self) -> 'Label':
|
||||||
"""
|
"""
|
||||||
Lock the Label
|
Lock the Label, causing any modifications to raise an exception.
|
||||||
|
|
||||||
:return: self
|
Return:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
object.__setattr__(self, 'locked', True)
|
object.__setattr__(self, 'locked', True)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def unlock(self) -> 'Label':
|
def unlock(self) -> 'Label':
|
||||||
"""
|
"""
|
||||||
Unlock the Label
|
Unlock the Label, re-allowing changes.
|
||||||
|
|
||||||
:return: self
|
Return:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
object.__setattr__(self, 'locked', False)
|
object.__setattr__(self, 'locked', False)
|
||||||
return self
|
return self
|
||||||
|
@ -27,22 +27,32 @@ visitor_function_t = Callable[['Pattern', Tuple['Pattern'], Dict, numpy.ndarray]
|
|||||||
|
|
||||||
class Pattern:
|
class Pattern:
|
||||||
"""
|
"""
|
||||||
2D layout consisting of some set of shapes and references to other Pattern objects
|
2D layout consisting of some set of shapes, labels, and references to other Pattern objects
|
||||||
(via SubPattern). Shapes are assumed to inherit from .shapes.Shape or provide equivalent
|
(via SubPattern and GridRepetition). Shapes are assumed to inherit from
|
||||||
functions.
|
masque.shapes.Shape or provide equivalent functions.
|
||||||
|
|
||||||
:var shapes: List of all shapes in this Pattern. Elements in this list are assumed to inherit
|
|
||||||
from Shape or provide equivalent functions.
|
|
||||||
:var subpatterns: List of all SubPattern objects in this Pattern. Multiple SubPattern objects
|
|
||||||
may reference the same Pattern object.
|
|
||||||
:var name: An identifier for this object. Not necessarily unique.
|
|
||||||
"""
|
"""
|
||||||
__slots__ = ('shapes', 'labels', 'subpatterns', 'name', 'locked')
|
__slots__ = ('shapes', 'labels', 'subpatterns', 'name', 'locked')
|
||||||
|
|
||||||
shapes: List[Shape]
|
shapes: List[Shape]
|
||||||
|
""" List of all shapes in this Pattern.
|
||||||
|
Elements in this list are assumed to inherit from Shape or provide equivalent functions.
|
||||||
|
"""
|
||||||
|
|
||||||
labels: List[Label]
|
labels: List[Label]
|
||||||
|
""" List of all labels in this Pattern. """
|
||||||
|
|
||||||
subpatterns: List[SubPattern or GridRepetition]
|
subpatterns: List[SubPattern or GridRepetition]
|
||||||
|
""" List of all objects referencing other patterns in this Pattern.
|
||||||
|
Examples are SubPattern (gdsii "instances") or GridRepetition (gdsii "arrays")
|
||||||
|
Multiple objects in this list may reference the same Pattern object
|
||||||
|
(multiple instances of the same object).
|
||||||
|
"""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
|
""" A name for this pattern """
|
||||||
|
|
||||||
locked: bool
|
locked: bool
|
||||||
|
""" When the pattern is locked, no changes may be made. """
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
name: str = '',
|
name: str = '',
|
||||||
@ -55,11 +65,12 @@ class Pattern:
|
|||||||
Basic init; arguments get assigned to member variables.
|
Basic init; arguments get assigned to member variables.
|
||||||
Non-list inputs for shapes and subpatterns get converted to lists.
|
Non-list inputs for shapes and subpatterns get converted to lists.
|
||||||
|
|
||||||
:param shapes: Initial shapes in the Pattern
|
Args:
|
||||||
:param labels: Initial labels in the Pattern
|
shapes: Initial shapes in the Pattern
|
||||||
:param subpatterns: Initial subpatterns in the Pattern
|
labels: Initial labels in the Pattern
|
||||||
:param name: An identifier for the Pattern
|
subpatterns: Initial subpatterns in the Pattern
|
||||||
:param locked: Whether to lock the pattern after construction
|
name: An identifier for the Pattern
|
||||||
|
locked: Whether to lock the pattern after construction
|
||||||
"""
|
"""
|
||||||
self.unlock()
|
self.unlock()
|
||||||
if isinstance(shapes, list):
|
if isinstance(shapes, list):
|
||||||
@ -106,8 +117,11 @@ class Pattern:
|
|||||||
Appends all shapes, labels and subpatterns from other_pattern to self's shapes,
|
Appends all shapes, labels and subpatterns from other_pattern to self's shapes,
|
||||||
labels, and supbatterns.
|
labels, and supbatterns.
|
||||||
|
|
||||||
:param other_pattern: The Pattern to append
|
Args:
|
||||||
:return: self
|
other_pattern: The Pattern to append
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
self.subpatterns += other_pattern.subpatterns
|
self.subpatterns += other_pattern.subpatterns
|
||||||
self.shapes += other_pattern.shapes
|
self.shapes += other_pattern.shapes
|
||||||
@ -125,15 +139,18 @@ class Pattern:
|
|||||||
given entity_func returns True.
|
given entity_func returns True.
|
||||||
Self is _not_ altered, but shapes, labels, and subpatterns are _not_ copied.
|
Self is _not_ altered, but shapes, labels, and subpatterns are _not_ copied.
|
||||||
|
|
||||||
:param shapes_func: Given a shape, returns a boolean denoting whether the shape is a member
|
Args:
|
||||||
|
shapes_func: Given a shape, returns a boolean denoting whether the shape is a member
|
||||||
of the subset. Default always returns False.
|
of the subset. Default always returns False.
|
||||||
:param labels_func: Given a label, returns a boolean denoting whether the label is a member
|
labels_func: Given a label, returns a boolean denoting whether the label is a member
|
||||||
of the subset. Default always returns False.
|
of the subset. Default always returns False.
|
||||||
:param subpatterns_func: Given a subpattern, returns a boolean denoting if it is a member
|
subpatterns_func: Given a subpattern, returns a boolean denoting if it is a member
|
||||||
of the subset. Default always returns False.
|
of the subset. Default always returns False.
|
||||||
:param recursive: If True, also calls .subset() recursively on patterns referenced by this
|
recursive: If True, also calls .subset() recursively on patterns referenced by this
|
||||||
pattern.
|
pattern.
|
||||||
:return: A Pattern containing all the shapes and subpatterns for which the parameter
|
|
||||||
|
Returns:
|
||||||
|
A Pattern containing all the shapes and subpatterns for which the parameter
|
||||||
functions return True
|
functions return True
|
||||||
"""
|
"""
|
||||||
def do_subset(src):
|
def do_subset(src):
|
||||||
@ -163,12 +180,17 @@ class Pattern:
|
|||||||
It is only applied to any given pattern once, regardless of how many times it is
|
It is only applied to any given pattern once, regardless of how many times it is
|
||||||
referenced.
|
referenced.
|
||||||
|
|
||||||
:param func: Function which accepts a Pattern, and returns a pattern.
|
Args:
|
||||||
:param memo: Dictionary used to avoid re-running on multiply-referenced patterns.
|
func: Function which accepts a Pattern, and returns a pattern.
|
||||||
Stores {id(pattern): func(pattern)} for patterns which have already been processed.
|
memo: Dictionary used to avoid re-running on multiply-referenced patterns.
|
||||||
Default None (no already-processed patterns).
|
Stores `{id(pattern): func(pattern)}` for patterns which have already been processed.
|
||||||
:return: The result of applying func() to this pattern and all subpatterns.
|
Default `None` (no already-processed patterns).
|
||||||
:raises: PatternError if called on a pattern containing a circular reference.
|
|
||||||
|
Returns:
|
||||||
|
The result of applying func() to this pattern and all subpatterns.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
PatternError if called on a pattern containing a circular reference.
|
||||||
"""
|
"""
|
||||||
if memo is None:
|
if memo is None:
|
||||||
memo = {}
|
memo = {}
|
||||||
@ -212,19 +234,24 @@ class Pattern:
|
|||||||
for the instance being visited
|
for the instance being visited
|
||||||
`memo`: Arbitrary dict (not altered except by visit_*())
|
`memo`: Arbitrary dict (not altered except by visit_*())
|
||||||
|
|
||||||
:param visit_before: Function to call before traversing subpatterns.
|
Args:
|
||||||
|
visit_before: Function to call before traversing subpatterns.
|
||||||
|
Should accept a `Pattern` and `**visit_args`, and return the (possibly modified)
|
||||||
|
pattern. Default `None` (not called).
|
||||||
|
visit_after: Function to call after traversing subpatterns.
|
||||||
Should accept a Pattern and **visit_args, and return the (possibly modified)
|
Should accept a Pattern and **visit_args, and return the (possibly modified)
|
||||||
pattern. Default None (not called).
|
pattern. Default `None` (not called).
|
||||||
:param visit_after: Function to call after traversing subpatterns.
|
transform: Initial value for `visit_args['transform']`.
|
||||||
Should accept a Pattern and **visit_args, and return the (possibly modified)
|
|
||||||
pattern. Default None (not called).
|
|
||||||
:param transform: Initial value for `visit_args['transform']`.
|
|
||||||
Can be `False`, in which case the transform is not calculated.
|
Can be `False`, in which case the transform is not calculated.
|
||||||
`True` or `None` is interpreted as [0, 0, 0, 0].
|
`True` or `None` is interpreted as `[0, 0, 0, 0]`.
|
||||||
:param memo: Arbitrary dict for use by visit_*() functions. Default None (empty dict).
|
memo: Arbitrary dict for use by `visit_*()` functions. Default `None` (empty dict).
|
||||||
:param hierarchy: Tuple of patterns specifying the hierarchy above the current pattern.
|
hierarchy: Tuple of patterns specifying the hierarchy above the current pattern.
|
||||||
Appended to the start of the generated `visit_args['hierarchy']`.
|
Appended to the start of the generated `visit_args['hierarchy']`.
|
||||||
Default is an empty tuple.
|
Default is an empty tuple.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The result, including `visit_before(self, ...)` and `visit_after(self, ...)`.
|
||||||
|
Note that `self` may also be altered!
|
||||||
"""
|
"""
|
||||||
if memo is None:
|
if memo is None:
|
||||||
memo = {}
|
memo = {}
|
||||||
@ -267,16 +294,19 @@ class Pattern:
|
|||||||
poly_max_arclen: float = None,
|
poly_max_arclen: float = None,
|
||||||
) -> 'Pattern':
|
) -> 'Pattern':
|
||||||
"""
|
"""
|
||||||
Calls .to_polygons(...) on all the shapes in this Pattern and any referenced patterns,
|
Calls `.to_polygons(...)` on all the shapes in this Pattern and any referenced patterns,
|
||||||
replacing them with the returned polygons.
|
replacing them with the returned polygons.
|
||||||
Arguments are passed directly to shape.to_polygons(...).
|
Arguments are passed directly to `shape.to_polygons(...)`.
|
||||||
|
|
||||||
:param poly_num_points: Number of points to use for each polygon. Can be overridden by
|
Args:
|
||||||
poly_max_arclen if that results in more points. Optional, defaults to shapes'
|
poly_num_points: Number of points to use for each polygon. Can be overridden by
|
||||||
|
`poly_max_arclen` if that results in more points. Optional, defaults to shapes'
|
||||||
internal defaults.
|
internal defaults.
|
||||||
:param poly_max_arclen: Maximum arclength which can be approximated by a single line
|
poly_max_arclen: Maximum arclength which can be approximated by a single line
|
||||||
segment. Optional, defaults to shapes' internal defaults.
|
segment. Optional, defaults to shapes' internal defaults.
|
||||||
:return: self
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
old_shapes = self.shapes
|
old_shapes = self.shapes
|
||||||
self.shapes = list(itertools.chain.from_iterable(
|
self.shapes = list(itertools.chain.from_iterable(
|
||||||
@ -291,12 +321,15 @@ class Pattern:
|
|||||||
grid_y: numpy.ndarray,
|
grid_y: numpy.ndarray,
|
||||||
) -> 'Pattern':
|
) -> 'Pattern':
|
||||||
"""
|
"""
|
||||||
Calls .polygonize() and .flatten on the pattern, then calls .manhattanize() on all the
|
Calls `.polygonize()` and `.flatten()` on the pattern, then calls `.manhattanize()` on all the
|
||||||
resulting shapes, replacing them with the returned Manhattan polygons.
|
resulting shapes, replacing them with the returned Manhattan polygons.
|
||||||
|
|
||||||
:param grid_x: List of allowed x-coordinates for the Manhattanized polygon edges.
|
Args:
|
||||||
:param grid_y: List of allowed y-coordinates for the Manhattanized polygon edges.
|
grid_x: List of allowed x-coordinates for the Manhattanized polygon edges.
|
||||||
:return: self
|
grid_y: List of allowed y-coordinates for the Manhattanized polygon edges.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.polygonize().flatten()
|
self.polygonize().flatten()
|
||||||
@ -311,21 +344,25 @@ class Pattern:
|
|||||||
exclude_types: Tuple[Shape] = (Polygon,)
|
exclude_types: Tuple[Shape] = (Polygon,)
|
||||||
) -> 'Pattern':
|
) -> 'Pattern':
|
||||||
"""
|
"""
|
||||||
Iterates through this Pattern and all referenced Patterns. Within each Pattern, it iterates
|
Iterates through this `Pattern` and all referenced `Pattern`s. Within each `Pattern`, it iterates
|
||||||
over all shapes, calling .normalized_form(norm_value) on them to retrieve a scale-,
|
over all shapes, calling `.normalized_form(norm_value)` on them to retrieve a scale-,
|
||||||
offset-, dose-, and rotation-independent form. Each shape whose normalized form appears
|
offset-, dose-, and rotation-independent form. Each shape whose normalized form appears
|
||||||
more than once is removed and re-added using subpattern objects referencing a newly-created
|
more than once is removed and re-added using subpattern objects referencing a newly-created
|
||||||
Pattern containing only the normalized form of the shape.
|
`Pattern` containing only the normalized form of the shape.
|
||||||
|
|
||||||
Note that the default norm_value was chosen to give a reasonable precision when converting
|
Note:
|
||||||
|
The default norm_value was chosen to give a reasonable precision when converting
|
||||||
to GDSII, which uses integer values for pixel coordinates.
|
to GDSII, which uses integer values for pixel coordinates.
|
||||||
|
|
||||||
:param recursive: Whether to call recursively on self's subpatterns. Default True.
|
Args:
|
||||||
:param norm_value: Passed to shape.normalized_form(norm_value). Default 1e6 (see function
|
recursive: Whether to call recursively on self's subpatterns. Default `True`.
|
||||||
|
norm_value: Passed to `shape.normalized_form(norm_value)`. Default `1e6` (see function
|
||||||
note about GDSII)
|
note about GDSII)
|
||||||
:param exclude_types: Shape types passed in this argument are always left untouched, for
|
exclude_types: Shape types passed in this argument are always left untouched, for
|
||||||
speed or convenience. Default: (Shapes.Polygon,)
|
speed or convenience. Default: `(shapes.Polygon,)`
|
||||||
:return: self
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if exclude_types is None:
|
if exclude_types is None:
|
||||||
@ -337,9 +374,9 @@ class Pattern:
|
|||||||
norm_value=norm_value,
|
norm_value=norm_value,
|
||||||
exclude_types=exclude_types)
|
exclude_types=exclude_types)
|
||||||
|
|
||||||
# Create a dict which uses the label tuple from .normalized_form() as a key, and which
|
# Create a dict which uses the label tuple from `.normalized_form()` as a key, and which
|
||||||
# stores (function_to_create_normalized_shape, [(index_in_shapes, values), ...]), where
|
# stores `(function_to_create_normalized_shape, [(index_in_shapes, values), ...])`, where
|
||||||
# values are the (offset, scale, rotation, dose) values as calculated by .normalized_form()
|
# values are the `(offset, scale, rotation, dose)` values as calculated by `.normalized_form()`
|
||||||
shape_table = defaultdict(lambda: [None, list()])
|
shape_table = defaultdict(lambda: [None, list()])
|
||||||
for i, shape in enumerate(self.shapes):
|
for i, shape in enumerate(self.shapes):
|
||||||
if not any((isinstance(shape, t) for t in exclude_types)):
|
if not any((isinstance(shape, t) for t in exclude_types)):
|
||||||
@ -348,9 +385,9 @@ class Pattern:
|
|||||||
shape_table[label][1].append((i, values))
|
shape_table[label][1].append((i, values))
|
||||||
|
|
||||||
# Iterate over the normalized shapes in the table. If any normalized shape occurs more than
|
# Iterate over the normalized shapes in the table. If any normalized shape occurs more than
|
||||||
# once, create a Pattern holding a normalized shape object, and add self.subpatterns
|
# once, create a `Pattern` holding a normalized shape object, and add `self.subpatterns`
|
||||||
# entries for each occurrence in self. Also, note down that we should delete the
|
# entries for each occurrence in self. Also, note down that we should delete the
|
||||||
# self.shapes entries for which we made SubPatterns.
|
# `self.shapes` entries for which we made SubPatterns.
|
||||||
shapes_to_remove = []
|
shapes_to_remove = []
|
||||||
for label in shape_table:
|
for label in shape_table:
|
||||||
if len(shape_table[label][1]) > 1:
|
if len(shape_table[label][1]) > 1:
|
||||||
@ -374,21 +411,23 @@ class Pattern:
|
|||||||
"""
|
"""
|
||||||
Represents the pattern as a list of polygons.
|
Represents the pattern as a list of polygons.
|
||||||
|
|
||||||
Deep-copies the pattern, then calls .polygonize() and .flatten() on the copy in order to
|
Deep-copies the pattern, then calls `.polygonize()` and `.flatten()` on the copy in order to
|
||||||
generate the list of polygons.
|
generate the list of polygons.
|
||||||
|
|
||||||
:return: A list of (Ni, 2) numpy.ndarrays specifying vertices of the polygons. Each ndarray
|
Returns:
|
||||||
is of the form [[x0, y0], [x1, y1],...].
|
A list of `(Ni, 2)` `numpy.ndarray`s specifying vertices of the polygons. Each ndarray
|
||||||
|
is of the form `[[x0, y0], [x1, y1],...]`.
|
||||||
"""
|
"""
|
||||||
pat = self.deepcopy().deepunlock().polygonize().flatten()
|
pat = self.deepcopy().deepunlock().polygonize().flatten()
|
||||||
return [shape.vertices + shape.offset for shape in pat.shapes]
|
return [shape.vertices + shape.offset for shape in pat.shapes]
|
||||||
|
|
||||||
def referenced_patterns_by_id(self) -> Dict[int, 'Pattern']:
|
def referenced_patterns_by_id(self) -> Dict[int, 'Pattern']:
|
||||||
"""
|
"""
|
||||||
Create a dictionary of {id(pat): pat} for all Pattern objects referenced by this
|
Create a dictionary of `{id(pat): pat}` for all Pattern objects referenced by this
|
||||||
Pattern (operates recursively on all referenced Patterns as well)
|
Pattern (operates recursively on all referenced Patterns as well)
|
||||||
|
|
||||||
:return: Dictionary of {id(pat): pat} for all referenced Pattern objects
|
Returns:
|
||||||
|
Dictionary of `{id(pat): pat}` for all referenced Pattern objects
|
||||||
"""
|
"""
|
||||||
ids = {}
|
ids = {}
|
||||||
for subpat in self.subpatterns:
|
for subpat in self.subpatterns:
|
||||||
@ -399,11 +438,12 @@ class Pattern:
|
|||||||
|
|
||||||
def get_bounds(self) -> Union[numpy.ndarray, None]:
|
def get_bounds(self) -> Union[numpy.ndarray, None]:
|
||||||
"""
|
"""
|
||||||
Return a numpy.ndarray containing [[x_min, y_min], [x_max, y_max]], corresponding to the
|
Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the
|
||||||
extent of the Pattern's contents in each dimension.
|
extent of the Pattern's contents in each dimension.
|
||||||
Returns None if the Pattern is empty.
|
Returns `None` if the Pattern is empty.
|
||||||
|
|
||||||
:return: [[x_min, y_min], [x_max, y_max]] or None
|
Returns:
|
||||||
|
`[[x_min, y_min], [x_max, y_max]]` or `None`
|
||||||
"""
|
"""
|
||||||
entries = self.shapes + self.subpatterns + self.labels
|
entries = self.shapes + self.subpatterns + self.labels
|
||||||
if not entries:
|
if not entries:
|
||||||
@ -428,13 +468,16 @@ class Pattern:
|
|||||||
|
|
||||||
Shape identifiers are changed to represent their original position in the
|
Shape identifiers are changed to represent their original position in the
|
||||||
pattern hierarchy:
|
pattern hierarchy:
|
||||||
(L1_name (str), L1_index (int), L2_name, L2_index, ..., *original_shape_identifier)
|
`(L1_name (str), L1_index (int), L2_name, L2_index, ..., *original_shape_identifier)`
|
||||||
where L1_name is the first-level subpattern's name (e.g. self.subpatterns[0].pattern.name),
|
where
|
||||||
L2_name is the next-level subpattern's name (e.g.
|
`L1_name` is the first-level subpattern's name (e.g. `self.subpatterns[0].pattern.name`),
|
||||||
self.subpatterns[0].pattern.subpatterns[0].pattern.name) and L1_index is an integer
|
`L2_name` is the next-level subpattern's name (e.g.
|
||||||
used to differentiate between multiple instance of the same (or same-named) subpatterns.
|
`self.subpatterns[0].pattern.subpatterns[0].pattern.name`) and
|
||||||
|
`L1_index` is an integer used to differentiate between multiple instance ofi the same
|
||||||
|
(or same-named) subpatterns.
|
||||||
|
|
||||||
:return: self
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
subpatterns = copy.deepcopy(self.subpatterns)
|
subpatterns = copy.deepcopy(self.subpatterns)
|
||||||
self.subpatterns = []
|
self.subpatterns = []
|
||||||
@ -457,22 +500,28 @@ class Pattern:
|
|||||||
"""
|
"""
|
||||||
Translates all shapes, label, and subpatterns by the given offset.
|
Translates all shapes, label, and subpatterns by the given offset.
|
||||||
|
|
||||||
:param offset: Offset to translate by
|
Args:
|
||||||
:return: self
|
offset: (x, y) to translate by
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
for entry in self.shapes + self.subpatterns + self.labels:
|
for entry in self.shapes + self.subpatterns + self.labels:
|
||||||
entry.translate(offset)
|
entry.translate(offset)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def scale_elements(self, scale: float) -> 'Pattern':
|
def scale_elements(self, c: float) -> 'Pattern':
|
||||||
""""
|
""""
|
||||||
Scales all shapes and subpatterns by the given value.
|
Scales all shapes and subpatterns by the given value.
|
||||||
|
|
||||||
:param scale: value to scale by
|
Args:
|
||||||
:return: self
|
c: factor to scale by
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
for entry in self.shapes + self.subpatterns:
|
for entry in self.shapes + self.subpatterns:
|
||||||
entry.scale(scale)
|
entry.scale(c)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def scale_by(self, c: float) -> 'Pattern':
|
def scale_by(self, c: float) -> 'Pattern':
|
||||||
@ -480,8 +529,11 @@ class Pattern:
|
|||||||
Scale this Pattern by the given value
|
Scale this Pattern by the given value
|
||||||
(all shapes and subpatterns and their offsets are scaled)
|
(all shapes and subpatterns and their offsets are scaled)
|
||||||
|
|
||||||
:param c: value to scale by
|
Args:
|
||||||
:return: self
|
c: factor to scale by
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
for entry in self.shapes + self.subpatterns:
|
for entry in self.shapes + self.subpatterns:
|
||||||
entry.offset *= c
|
entry.offset *= c
|
||||||
@ -494,9 +546,12 @@ class Pattern:
|
|||||||
"""
|
"""
|
||||||
Rotate the Pattern around the a location.
|
Rotate the Pattern around the a location.
|
||||||
|
|
||||||
:param pivot: Location to rotate around
|
Args:
|
||||||
:param rotation: Angle to rotate by (counter-clockwise, radians)
|
pivot: (x, y) location to rotate around
|
||||||
:return: self
|
rotation: Angle to rotate by (counter-clockwise, radians)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
pivot = numpy.array(pivot)
|
pivot = numpy.array(pivot)
|
||||||
self.translate_elements(-pivot)
|
self.translate_elements(-pivot)
|
||||||
@ -509,8 +564,11 @@ class Pattern:
|
|||||||
"""
|
"""
|
||||||
Rotate the offsets of all shapes, labels, and subpatterns around (0, 0)
|
Rotate the offsets of all shapes, labels, and subpatterns around (0, 0)
|
||||||
|
|
||||||
:param rotation: Angle to rotate by (counter-clockwise, radians)
|
Args:
|
||||||
:return: self
|
rotation: Angle to rotate by (counter-clockwise, radians)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
for entry in self.shapes + self.subpatterns + self.labels:
|
for entry in self.shapes + self.subpatterns + self.labels:
|
||||||
entry.offset = numpy.dot(rotation_matrix_2d(rotation), entry.offset)
|
entry.offset = numpy.dot(rotation_matrix_2d(rotation), entry.offset)
|
||||||
@ -520,8 +578,11 @@ class Pattern:
|
|||||||
"""
|
"""
|
||||||
Rotate each shape and subpattern around its center (offset)
|
Rotate each shape and subpattern around its center (offset)
|
||||||
|
|
||||||
:param rotation: Angle to rotate by (counter-clockwise, radians)
|
Args:
|
||||||
:return: self
|
rotation: Angle to rotate by (counter-clockwise, radians)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
for entry in self.shapes + self.subpatterns:
|
for entry in self.shapes + self.subpatterns:
|
||||||
entry.rotate(rotation)
|
entry.rotate(rotation)
|
||||||
@ -531,8 +592,12 @@ class Pattern:
|
|||||||
"""
|
"""
|
||||||
Mirror the offsets of all shapes, labels, and subpatterns across an axis
|
Mirror the offsets of all shapes, labels, and subpatterns across an axis
|
||||||
|
|
||||||
:param axis: Axis to mirror across
|
Args:
|
||||||
:return: self
|
axis: Axis to mirror across
|
||||||
|
(0: mirror across x axis, 1: mirror across y axis)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
for entry in self.shapes + self.subpatterns + self.labels:
|
for entry in self.shapes + self.subpatterns + self.labels:
|
||||||
entry.offset[axis - 1] *= -1
|
entry.offset[axis - 1] *= -1
|
||||||
@ -541,10 +606,14 @@ class Pattern:
|
|||||||
def mirror_elements(self, axis: int) -> 'Pattern':
|
def mirror_elements(self, axis: int) -> 'Pattern':
|
||||||
"""
|
"""
|
||||||
Mirror each shape and subpattern across an axis, relative to its
|
Mirror each shape and subpattern across an axis, relative to its
|
||||||
center (offset)
|
offset
|
||||||
|
|
||||||
:param axis: Axis to mirror across
|
Args:
|
||||||
:return: self
|
axis: Axis to mirror across
|
||||||
|
(0: mirror across x axis, 1: mirror across y axis)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
for entry in self.shapes + self.subpatterns:
|
for entry in self.shapes + self.subpatterns:
|
||||||
entry.mirror(axis)
|
entry.mirror(axis)
|
||||||
@ -554,22 +623,29 @@ class Pattern:
|
|||||||
"""
|
"""
|
||||||
Mirror the Pattern across an axis
|
Mirror the Pattern across an axis
|
||||||
|
|
||||||
:param axis: Axis to mirror across
|
Args:
|
||||||
:return: self
|
axis: Axis to mirror across
|
||||||
|
(0: mirror across x axis, 1: mirror across y axis)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
self.mirror_elements(axis)
|
self.mirror_elements(axis)
|
||||||
self.mirror_element_centers(axis)
|
self.mirror_element_centers(axis)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def scale_element_doses(self, factor: float) -> 'Pattern':
|
def scale_element_doses(self, c: float) -> 'Pattern':
|
||||||
"""
|
"""
|
||||||
Multiply all shape and subpattern doses by a factor
|
Multiply all shape and subpattern doses by a factor
|
||||||
|
|
||||||
:param factor: Factor to multiply doses by
|
Args:
|
||||||
:return: self
|
c: Factor to multiply doses by
|
||||||
|
|
||||||
|
Return:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
for entry in self.shapes + self.subpatterns:
|
for entry in self.shapes + self.subpatterns:
|
||||||
entry.dose *= factor
|
entry.dose *= c
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def copy(self) -> 'Pattern':
|
def copy(self) -> 'Pattern':
|
||||||
@ -577,25 +653,26 @@ class Pattern:
|
|||||||
Return a copy of the Pattern, deep-copying shapes and copying subpattern
|
Return a copy of the Pattern, deep-copying shapes and copying subpattern
|
||||||
entries, but not deep-copying any referenced patterns.
|
entries, but not deep-copying any referenced patterns.
|
||||||
|
|
||||||
See also: Pattern.deepcopy()
|
See also: `Pattern.deepcopy()`
|
||||||
|
|
||||||
:return: A copy of the current Pattern.
|
Returns:
|
||||||
|
A copy of the current Pattern.
|
||||||
"""
|
"""
|
||||||
return copy.copy(self)
|
return copy.copy(self)
|
||||||
|
|
||||||
def deepcopy(self) -> 'Pattern':
|
def deepcopy(self) -> 'Pattern':
|
||||||
"""
|
"""
|
||||||
Convenience method for copy.deepcopy(pattern)
|
Convenience method for `copy.deepcopy(pattern)`
|
||||||
|
|
||||||
:return: A deep copy of the current Pattern.
|
Returns:
|
||||||
|
A deep copy of the current Pattern.
|
||||||
"""
|
"""
|
||||||
return copy.deepcopy(self)
|
return copy.deepcopy(self)
|
||||||
|
|
||||||
def is_empty(self) -> bool:
|
def is_empty(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Returns true if the Pattern contains no shapes, labels, or subpatterns.
|
Returns:
|
||||||
|
True if the pattern is contains no shapes, labels, or subpatterns.
|
||||||
:return: True if the pattern is empty.
|
|
||||||
"""
|
"""
|
||||||
return (len(self.subpatterns) == 0 and
|
return (len(self.subpatterns) == 0 and
|
||||||
len(self.shapes) == 0 and
|
len(self.shapes) == 0 and
|
||||||
@ -603,9 +680,11 @@ class Pattern:
|
|||||||
|
|
||||||
def lock(self) -> 'Pattern':
|
def lock(self) -> 'Pattern':
|
||||||
"""
|
"""
|
||||||
Lock the pattern
|
Lock the pattern, raising an exception if it is modified.
|
||||||
|
Also see `deeplock()`.
|
||||||
|
|
||||||
:return: self
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
object.__setattr__(self, 'locked', True)
|
object.__setattr__(self, 'locked', True)
|
||||||
return self
|
return self
|
||||||
@ -614,16 +693,18 @@ class Pattern:
|
|||||||
"""
|
"""
|
||||||
Unlock the pattern
|
Unlock the pattern
|
||||||
|
|
||||||
:return: self
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
object.__setattr__(self, 'locked', False)
|
object.__setattr__(self, 'locked', False)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def deeplock(self) -> 'Pattern':
|
def deeplock(self) -> 'Pattern':
|
||||||
"""
|
"""
|
||||||
Recursively lock the pattern, all referenced shapes, subpatterns, and labels
|
Recursively lock the pattern, all referenced shapes, subpatterns, and labels.
|
||||||
|
|
||||||
:return: self
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
self.lock()
|
self.lock()
|
||||||
for ss in self.shapes + self.labels:
|
for ss in self.shapes + self.labels:
|
||||||
@ -634,11 +715,13 @@ class Pattern:
|
|||||||
|
|
||||||
def deepunlock(self) -> 'Pattern':
|
def deepunlock(self) -> 'Pattern':
|
||||||
"""
|
"""
|
||||||
Recursively unlock the pattern, all referenced shapes, subpatterns, and labels
|
Recursively unlock the pattern, all referenced shapes, subpatterns, and labels.
|
||||||
|
|
||||||
This is dangerous unless you have just performed a deepcopy!
|
This is dangerous unless you have just performed a deepcopy, since anything
|
||||||
|
you change will be changed everywhere it is referenced!
|
||||||
|
|
||||||
:return: self
|
Return:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
self.unlock()
|
self.unlock()
|
||||||
for ss in self.shapes + self.labels:
|
for ss in self.shapes + self.labels:
|
||||||
@ -650,10 +733,13 @@ class Pattern:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def load(filename: str) -> 'Pattern':
|
def load(filename: str) -> 'Pattern':
|
||||||
"""
|
"""
|
||||||
Load a Pattern from a file
|
Load a Pattern from a file using pickle
|
||||||
|
|
||||||
:param filename: Filename to load from
|
Args:
|
||||||
:return: Loaded Pattern
|
filename: Filename to load from
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Loaded Pattern
|
||||||
"""
|
"""
|
||||||
with open(filename, 'rb') as f:
|
with open(filename, 'rb') as f:
|
||||||
pattern = pickle.load(f)
|
pattern = pickle.load(f)
|
||||||
@ -662,10 +748,13 @@ class Pattern:
|
|||||||
|
|
||||||
def save(self, filename: str) -> 'Pattern':
|
def save(self, filename: str) -> 'Pattern':
|
||||||
"""
|
"""
|
||||||
Save the Pattern to a file
|
Save the Pattern to a file using pickle
|
||||||
|
|
||||||
:param filename: Filename to save to
|
Args:
|
||||||
:return: self
|
filename: Filename to save to
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
with open(filename, 'wb') as f:
|
with open(filename, 'wb') as f:
|
||||||
pickle.dump(self, f, protocol=pickle.HIGHEST_PROTOCOL)
|
pickle.dump(self, f, protocol=pickle.HIGHEST_PROTOCOL)
|
||||||
@ -679,12 +768,16 @@ class Pattern:
|
|||||||
"""
|
"""
|
||||||
Draw a picture of the Pattern and wait for the user to inspect it
|
Draw a picture of the Pattern and wait for the user to inspect it
|
||||||
|
|
||||||
Imports matplotlib.
|
Imports `matplotlib`.
|
||||||
|
|
||||||
:param offset: Coordinates to offset by before drawing
|
Note that this can be slow; it is often faster to export to GDSII and use
|
||||||
:param line_color: Outlines are drawn with this color (passed to matplotlib PolyCollection)
|
klayout or a different GDS viewer!
|
||||||
:param fill_color: Interiors are drawn with this color (passed to matplotlib PolyCollection)
|
|
||||||
:param overdraw: Whether to create a new figure or draw on a pre-existing one
|
Args:
|
||||||
|
offset: Coordinates to offset by before drawing
|
||||||
|
line_color: Outlines are drawn with this color (passed to `matplotlib.collections.PolyCollection`)
|
||||||
|
fill_color: Interiors are drawn with this color (passed to `matplotlib.collections.PolyCollection`)
|
||||||
|
overdraw: Whether to create a new figure or draw on a pre-existing one
|
||||||
"""
|
"""
|
||||||
# TODO: add text labels to visualize()
|
# TODO: add text labels to visualize()
|
||||||
from matplotlib import pyplot
|
from matplotlib import pyplot
|
||||||
|
@ -20,8 +20,8 @@ __author__ = 'Jan Petykiewicz'
|
|||||||
|
|
||||||
class GridRepetition:
|
class GridRepetition:
|
||||||
"""
|
"""
|
||||||
GridRepetition provides support for efficiently embedding multiple copies of a Pattern
|
GridRepetition provides support for efficiently embedding multiple copies of a `Pattern`
|
||||||
into another Pattern at regularly-spaced offsets.
|
into another `Pattern` at regularly-spaced offsets.
|
||||||
"""
|
"""
|
||||||
__slots__ = ('pattern',
|
__slots__ = ('pattern',
|
||||||
'_offset',
|
'_offset',
|
||||||
@ -37,24 +37,49 @@ class GridRepetition:
|
|||||||
'locked')
|
'locked')
|
||||||
|
|
||||||
pattern: 'Pattern'
|
pattern: 'Pattern'
|
||||||
|
""" The `Pattern` being instanced """
|
||||||
|
|
||||||
_offset: numpy.ndarray
|
_offset: numpy.ndarray
|
||||||
|
""" (x, y) offset for the base instance """
|
||||||
|
|
||||||
_dose: float
|
_dose: float
|
||||||
|
""" Dose factor """
|
||||||
|
|
||||||
_rotation: float
|
_rotation: float
|
||||||
''' Applies to individual instances in the grid, not the grid vectors '''
|
""" Rotation of the individual instances in the grid (not the grid vectors).
|
||||||
|
Radians, counterclockwise.
|
||||||
|
"""
|
||||||
|
|
||||||
_scale: float
|
_scale: float
|
||||||
''' Applies to individual instances in the grid, not the grid vectors '''
|
""" Scaling factor applied to individual instances in the grid (not the grid vectors) """
|
||||||
|
|
||||||
_mirrored: List[bool]
|
_mirrored: List[bool]
|
||||||
''' Applies to individual instances in the grid, not the grid vectors '''
|
""" Whether to mirror individual instances across the x and y axes
|
||||||
|
(Applies to individual instances in the grid, not the grid vectors)
|
||||||
|
"""
|
||||||
|
|
||||||
_a_vector: numpy.ndarray
|
_a_vector: numpy.ndarray
|
||||||
_b_vector: numpy.ndarray or None
|
""" Vector `[x, y]` specifying the first lattice vector of the grid.
|
||||||
|
Specifies center-to-center spacing between adjacent elements.
|
||||||
|
"""
|
||||||
|
|
||||||
_a_count: int
|
_a_count: int
|
||||||
|
""" Number of instances along the direction specified by the `a_vector` """
|
||||||
|
|
||||||
|
_b_vector: numpy.ndarray or None
|
||||||
|
""" Vector `[x, y]` specifying a second lattice vector for the grid.
|
||||||
|
Specifies center-to-center spacing between adjacent elements.
|
||||||
|
Can be `None` for a 1D array.
|
||||||
|
"""
|
||||||
|
|
||||||
_b_count: int
|
_b_count: int
|
||||||
|
""" Number of instances along the direction specified by the `b_vector` """
|
||||||
|
|
||||||
identifier: Tuple
|
identifier: Tuple
|
||||||
|
""" Arbitrary identifier """
|
||||||
|
|
||||||
locked: bool
|
locked: bool
|
||||||
|
""" If `True`, disallows changes to the GridRepetition """
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
pattern: 'Pattern',
|
pattern: 'Pattern',
|
||||||
@ -69,17 +94,20 @@ class GridRepetition:
|
|||||||
scale: float = 1.0,
|
scale: float = 1.0,
|
||||||
locked: bool = False):
|
locked: bool = False):
|
||||||
"""
|
"""
|
||||||
:param a_vector: First lattice vector, of the form [x, y].
|
Args:
|
||||||
|
a_vector: First lattice vector, of the form `[x, y]`.
|
||||||
Specifies center-to-center spacing between adjacent elements.
|
Specifies center-to-center spacing between adjacent elements.
|
||||||
:param a_count: Number of elements in the a_vector direction.
|
a_count: Number of elements in the a_vector direction.
|
||||||
:param b_vector: Second lattice vector, of the form [x, y].
|
b_vector: Second lattice vector, of the form `[x, y]`.
|
||||||
Specifies center-to-center spacing between adjacent elements.
|
Specifies center-to-center spacing between adjacent elements.
|
||||||
Can be omitted when specifying a 1D array.
|
Can be omitted when specifying a 1D array.
|
||||||
:param b_count: Number of elements in the b_vector direction.
|
b_count: Number of elements in the `b_vector` direction.
|
||||||
Should be omitted if b_vector was omitted.
|
Should be omitted if `b_vector` was omitted.
|
||||||
:param locked: Whether the subpattern is locked after initialization.
|
locked: Whether the `GridRepetition` is locked after initialization.
|
||||||
:raises: PatternError if b_* inputs conflict with each other
|
|
||||||
or a_count < 1.
|
Raises:
|
||||||
|
PatternError if `b_*` inputs conflict with each other
|
||||||
|
or `a_count < 1`.
|
||||||
"""
|
"""
|
||||||
if b_vector is None:
|
if b_vector is None:
|
||||||
if b_count > 1:
|
if b_count > 1:
|
||||||
@ -254,9 +282,11 @@ class GridRepetition:
|
|||||||
def as_pattern(self) -> 'Pattern':
|
def as_pattern(self) -> 'Pattern':
|
||||||
"""
|
"""
|
||||||
Returns a copy of self.pattern which has been scaled, rotated, repeated, etc.
|
Returns a copy of self.pattern which has been scaled, rotated, repeated, etc.
|
||||||
etc. according to this GridRepetitions's properties.
|
etc. according to this `GridRepetition`'s properties.
|
||||||
:return: Copy of self.pattern that has been repeated / altered as implied by
|
|
||||||
this object's other properties.
|
Returns:
|
||||||
|
A copy of self.pattern which has been scaled, rotated, repeated, etc.
|
||||||
|
etc. according to this `GridRepetition`'s properties.
|
||||||
"""
|
"""
|
||||||
patterns = []
|
patterns = []
|
||||||
|
|
||||||
@ -283,8 +313,11 @@ class GridRepetition:
|
|||||||
"""
|
"""
|
||||||
Translate by the given offset
|
Translate by the given offset
|
||||||
|
|
||||||
:param offset: Translate by this offset
|
Args:
|
||||||
:return: self
|
offset: `[x, y]` to translate by
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
self.offset += offset
|
self.offset += offset
|
||||||
return self
|
return self
|
||||||
@ -293,9 +326,12 @@ class GridRepetition:
|
|||||||
"""
|
"""
|
||||||
Rotate the array around a point
|
Rotate the array around a point
|
||||||
|
|
||||||
:param pivot: Point to rotate around
|
Args:
|
||||||
:param rotation: Angle to rotate by (counterclockwise, radians)
|
pivot: Point `[x, y]` to rotate around
|
||||||
:return: self
|
rotation: Angle to rotate by (counterclockwise, radians)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
pivot = numpy.array(pivot, dtype=float)
|
pivot = numpy.array(pivot, dtype=float)
|
||||||
self.translate(-pivot)
|
self.translate(-pivot)
|
||||||
@ -308,8 +344,11 @@ class GridRepetition:
|
|||||||
"""
|
"""
|
||||||
Rotate around (0, 0)
|
Rotate around (0, 0)
|
||||||
|
|
||||||
:param rotation: Angle to rotate by (counterclockwise, radians)
|
Args:
|
||||||
:return: self
|
rotation: Angle to rotate by (counterclockwise, radians)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
self.rotate_elements(rotation)
|
self.rotate_elements(rotation)
|
||||||
self.a_vector = numpy.dot(rotation_matrix_2d(rotation), self.a_vector)
|
self.a_vector = numpy.dot(rotation_matrix_2d(rotation), self.a_vector)
|
||||||
@ -321,8 +360,11 @@ class GridRepetition:
|
|||||||
"""
|
"""
|
||||||
Rotate each element around its origin
|
Rotate each element around its origin
|
||||||
|
|
||||||
:param rotation: Angle to rotate by (counterclockwise, radians)
|
Args:
|
||||||
:return: self
|
rotation: Angle to rotate by (counterclockwise, radians)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
self.rotation += rotation
|
self.rotation += rotation
|
||||||
return self
|
return self
|
||||||
@ -331,8 +373,12 @@ class GridRepetition:
|
|||||||
"""
|
"""
|
||||||
Mirror the GridRepetition across an axis.
|
Mirror the GridRepetition across an axis.
|
||||||
|
|
||||||
:param axis: Axis to mirror across.
|
Args:
|
||||||
:return: self
|
axis: Axis to mirror across.
|
||||||
|
(0: mirror across x-axis, 1: mirror across y-axis)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
self.mirror_elements(axis)
|
self.mirror_elements(axis)
|
||||||
self.a_vector[1-axis] *= -1
|
self.a_vector[1-axis] *= -1
|
||||||
@ -344,8 +390,12 @@ class GridRepetition:
|
|||||||
"""
|
"""
|
||||||
Mirror each element across an axis relative to its origin.
|
Mirror each element across an axis relative to its origin.
|
||||||
|
|
||||||
:param axis: Axis to mirror across.
|
Args:
|
||||||
:return: self
|
axis: Axis to mirror across.
|
||||||
|
(0: mirror across x-axis, 1: mirror across y-axis)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
self.mirrored[axis] = not self.mirrored[axis]
|
self.mirrored[axis] = not self.mirrored[axis]
|
||||||
self.rotation *= -1
|
self.rotation *= -1
|
||||||
@ -353,11 +403,12 @@ class GridRepetition:
|
|||||||
|
|
||||||
def get_bounds(self) -> numpy.ndarray or None:
|
def get_bounds(self) -> numpy.ndarray or None:
|
||||||
"""
|
"""
|
||||||
Return a numpy.ndarray containing [[x_min, y_min], [x_max, y_max]], corresponding to the
|
Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the
|
||||||
extent of the GridRepetition in each dimension.
|
extent of the `GridRepetition` in each dimension.
|
||||||
Returns None if the contained Pattern is empty.
|
Returns `None` if the contained `Pattern` is empty.
|
||||||
|
|
||||||
:return: [[x_min, y_min], [x_max, y_max]] or None
|
Returns:
|
||||||
|
`[[x_min, y_min], [x_max, y_max]]` or `None`
|
||||||
"""
|
"""
|
||||||
return self.as_pattern().get_bounds()
|
return self.as_pattern().get_bounds()
|
||||||
|
|
||||||
@ -365,7 +416,11 @@ class GridRepetition:
|
|||||||
"""
|
"""
|
||||||
Scale the GridRepetition by a factor
|
Scale the GridRepetition by a factor
|
||||||
|
|
||||||
:param c: scaling factor
|
Args:
|
||||||
|
c: scaling factor
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
self.scale_elements_by(c)
|
self.scale_elements_by(c)
|
||||||
self.a_vector *= c
|
self.a_vector *= c
|
||||||
@ -377,7 +432,11 @@ class GridRepetition:
|
|||||||
"""
|
"""
|
||||||
Scale each element by a factor
|
Scale each element by a factor
|
||||||
|
|
||||||
:param c: scaling factor
|
Args:
|
||||||
|
c: scaling factor
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
self.scale *= c
|
self.scale *= c
|
||||||
return self
|
return self
|
||||||
@ -386,7 +445,8 @@ class GridRepetition:
|
|||||||
"""
|
"""
|
||||||
Return a shallow copy of the repetition.
|
Return a shallow copy of the repetition.
|
||||||
|
|
||||||
:return: copy.copy(self)
|
Returns:
|
||||||
|
`copy.copy(self)`
|
||||||
"""
|
"""
|
||||||
return copy.copy(self)
|
return copy.copy(self)
|
||||||
|
|
||||||
@ -394,33 +454,37 @@ class GridRepetition:
|
|||||||
"""
|
"""
|
||||||
Return a deep copy of the repetition.
|
Return a deep copy of the repetition.
|
||||||
|
|
||||||
:return: copy.copy(self)
|
Returns:
|
||||||
|
`copy.deepcopy(self)`
|
||||||
"""
|
"""
|
||||||
return copy.deepcopy(self)
|
return copy.deepcopy(self)
|
||||||
|
|
||||||
def lock(self) -> 'GridRepetition':
|
def lock(self) -> 'GridRepetition':
|
||||||
"""
|
"""
|
||||||
Lock the GridRepetition
|
Lock the `GridRepetition`, disallowing changes.
|
||||||
|
|
||||||
:return: self
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
object.__setattr__(self, 'locked', True)
|
object.__setattr__(self, 'locked', True)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def unlock(self) -> 'GridRepetition':
|
def unlock(self) -> 'GridRepetition':
|
||||||
"""
|
"""
|
||||||
Unlock the GridRepetition
|
Unlock the `GridRepetition`
|
||||||
|
|
||||||
:return: self
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
object.__setattr__(self, 'locked', False)
|
object.__setattr__(self, 'locked', False)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def deeplock(self) -> 'GridRepetition':
|
def deeplock(self) -> 'GridRepetition':
|
||||||
"""
|
"""
|
||||||
Recursively lock the GridRepetition and its contained pattern
|
Recursively lock the `GridRepetition` and its contained pattern
|
||||||
|
|
||||||
:return: self
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
self.lock()
|
self.lock()
|
||||||
self.pattern.deeplock()
|
self.pattern.deeplock()
|
||||||
@ -428,11 +492,13 @@ class GridRepetition:
|
|||||||
|
|
||||||
def deepunlock(self) -> 'GridRepetition':
|
def deepunlock(self) -> 'GridRepetition':
|
||||||
"""
|
"""
|
||||||
Recursively unlock the GridRepetition and its contained pattern
|
Recursively unlock the `GridRepetition` and its contained pattern
|
||||||
|
|
||||||
This is dangerous unless you have just performed a deepcopy!
|
This is dangerous unless you have just performed a deepcopy, since
|
||||||
|
the component parts may be reused elsewhere.
|
||||||
|
|
||||||
:return: self
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
self.unlock()
|
self.unlock()
|
||||||
self.pattern.deepunlock()
|
self.pattern.deepunlock()
|
||||||
|
@ -24,19 +24,28 @@ class Arc(Shape):
|
|||||||
__slots__ = ('_radii', '_angles', '_width', '_rotation',
|
__slots__ = ('_radii', '_angles', '_width', '_rotation',
|
||||||
'poly_num_points', 'poly_max_arclen')
|
'poly_num_points', 'poly_max_arclen')
|
||||||
_radii: numpy.ndarray
|
_radii: numpy.ndarray
|
||||||
_angles: numpy.ndarray
|
""" Two radii for defining an ellipse """
|
||||||
_width: float
|
|
||||||
_rotation: float
|
_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
|
poly_num_points: int
|
||||||
|
""" Sets the default number of points for `.polygonize()` """
|
||||||
|
|
||||||
poly_max_arclen: float
|
poly_max_arclen: float
|
||||||
|
""" Sets the default max segement length for `.polygonize()` """
|
||||||
|
|
||||||
# radius properties
|
# radius properties
|
||||||
@property
|
@property
|
||||||
def radii(self) -> numpy.ndarray:
|
def radii(self) -> numpy.ndarray:
|
||||||
"""
|
"""
|
||||||
Return the radii [rx, ry]
|
Return the radii `[rx, ry]`
|
||||||
|
|
||||||
:return: [rx, ry]
|
|
||||||
"""
|
"""
|
||||||
return self._radii
|
return self._radii
|
||||||
|
|
||||||
@ -73,10 +82,11 @@ class Arc(Shape):
|
|||||||
@property
|
@property
|
||||||
def angles(self) -> vector2:
|
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
|
Angles are measured from x-axis after rotation
|
||||||
|
|
||||||
:return: [a_start, a_stop]
|
Returns:
|
||||||
|
`[a_start, a_stop]`
|
||||||
"""
|
"""
|
||||||
return self._angles
|
return self._angles
|
||||||
|
|
||||||
@ -109,7 +119,8 @@ class Arc(Shape):
|
|||||||
"""
|
"""
|
||||||
Rotation of radius_x from x_axis, counterclockwise, in radians. Stored mod 2*pi
|
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
|
return self._rotation
|
||||||
|
|
||||||
@ -125,7 +136,8 @@ class Arc(Shape):
|
|||||||
"""
|
"""
|
||||||
Width of the arc (difference between inner and outer radii)
|
Width of the arc (difference between inner and outer radii)
|
||||||
|
|
||||||
:return: width
|
Returns:
|
||||||
|
width
|
||||||
"""
|
"""
|
||||||
return self._width
|
return self._width
|
||||||
|
|
||||||
@ -225,12 +237,12 @@ class Arc(Shape):
|
|||||||
def get_bounds(self) -> numpy.ndarray:
|
def get_bounds(self) -> numpy.ndarray:
|
||||||
'''
|
'''
|
||||||
Equation for rotated ellipse is
|
Equation for rotated ellipse is
|
||||||
x = x0 + a * cos(t) * cos(rot) - b * sin(t) * sin(phi)
|
`x = x0 + a * cos(t) * cos(rot) - b * sin(t) * sin(phi)`
|
||||||
y = y0 + a * cos(t) * sin(rot) + b * sin(t) * cos(rot)
|
`y = y0 + a * cos(t) * sin(rot) + b * sin(t) * cos(rot)`
|
||||||
where t is our parameter.
|
where `t` is our parameter.
|
||||||
|
|
||||||
Differentiating and solving for 0 slope wrt. t, we find
|
Differentiating and solving for 0 slope wrt. `t`, we find
|
||||||
tan(t) = -+ b/a cot(phi)
|
`tan(t) = -+ b/a cot(phi)`
|
||||||
where -+ is for x, y cases, so that's where the extrema are.
|
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.
|
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:
|
def get_cap_edges(self) -> numpy.ndarray:
|
||||||
'''
|
'''
|
||||||
:returns: [[[x0, y0], [x1, y1]], array of 4 points, specifying the two cuts which
|
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.
|
[[x2, y2], [x3, y3]]], would create this arc from its corresponding ellipse.
|
||||||
|
```
|
||||||
'''
|
'''
|
||||||
a_ranges = self._angles_to_parameters()
|
a_ranges = self._angles_to_parameters()
|
||||||
|
|
||||||
@ -356,8 +371,9 @@ class Arc(Shape):
|
|||||||
|
|
||||||
def _angles_to_parameters(self) -> numpy.ndarray:
|
def _angles_to_parameters(self) -> numpy.ndarray:
|
||||||
'''
|
'''
|
||||||
:return: "Eccentric anomaly" parameter ranges for the inner and outer edges, in the form
|
Returns:
|
||||||
[[a_min_inner, a_max_inner], [a_min_outer, a_max_outer]]
|
"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 = []
|
a = []
|
||||||
for sgn in (-1, +1):
|
for sgn in (-1, +1):
|
||||||
|
@ -17,16 +17,19 @@ class Circle(Shape):
|
|||||||
"""
|
"""
|
||||||
__slots__ = ('_radius', 'poly_num_points', 'poly_max_arclen')
|
__slots__ = ('_radius', 'poly_num_points', 'poly_max_arclen')
|
||||||
_radius: float
|
_radius: float
|
||||||
|
""" Circle radius """
|
||||||
|
|
||||||
poly_num_points: int
|
poly_num_points: int
|
||||||
|
""" Sets the default number of points for `.polygonize()` """
|
||||||
|
|
||||||
poly_max_arclen: float
|
poly_max_arclen: float
|
||||||
|
""" Sets the default max segement length for `.polygonize()` """
|
||||||
|
|
||||||
# radius property
|
# radius property
|
||||||
@property
|
@property
|
||||||
def radius(self) -> float:
|
def radius(self) -> float:
|
||||||
"""
|
"""
|
||||||
Circle's radius (float, >= 0)
|
Circle's radius (float, >= 0)
|
||||||
|
|
||||||
:return: radius
|
|
||||||
"""
|
"""
|
||||||
return self._radius
|
return self._radius
|
||||||
|
|
||||||
|
@ -20,17 +20,22 @@ class Ellipse(Shape):
|
|||||||
__slots__ = ('_radii', '_rotation',
|
__slots__ = ('_radii', '_rotation',
|
||||||
'poly_num_points', 'poly_max_arclen')
|
'poly_num_points', 'poly_max_arclen')
|
||||||
_radii: numpy.ndarray
|
_radii: numpy.ndarray
|
||||||
|
""" Ellipse radii """
|
||||||
|
|
||||||
_rotation: float
|
_rotation: float
|
||||||
|
""" Angle from x-axis to first radius (ccw, radians) """
|
||||||
|
|
||||||
poly_num_points: int
|
poly_num_points: int
|
||||||
|
""" Sets the default number of points for `.polygonize()` """
|
||||||
|
|
||||||
poly_max_arclen: float
|
poly_max_arclen: float
|
||||||
|
""" Sets the default max segement length for `.polygonize()` """
|
||||||
|
|
||||||
# radius properties
|
# radius properties
|
||||||
@property
|
@property
|
||||||
def radii(self) -> numpy.ndarray:
|
def radii(self) -> numpy.ndarray:
|
||||||
"""
|
"""
|
||||||
Return the radii [rx, ry]
|
Return the radii `[rx, ry]`
|
||||||
|
|
||||||
:return: [rx, ry]
|
|
||||||
"""
|
"""
|
||||||
return self._radii
|
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
|
Rotation of rx from the x axis. Uses the interval [0, pi) in radians (counterclockwise
|
||||||
is positive)
|
is positive)
|
||||||
|
|
||||||
:return: counterclockwise rotation in radians
|
Returns:
|
||||||
|
counterclockwise rotation in radians
|
||||||
"""
|
"""
|
||||||
return self._rotation
|
return self._rotation
|
||||||
|
|
||||||
|
@ -37,8 +37,6 @@ class Path(Shape):
|
|||||||
def width(self) -> float:
|
def width(self) -> float:
|
||||||
"""
|
"""
|
||||||
Path width (float, >= 0)
|
Path width (float, >= 0)
|
||||||
|
|
||||||
:return: width
|
|
||||||
"""
|
"""
|
||||||
return self._width
|
return self._width
|
||||||
|
|
||||||
@ -55,8 +53,6 @@ class Path(Shape):
|
|||||||
def cap(self) -> 'Path.Cap':
|
def cap(self) -> 'Path.Cap':
|
||||||
"""
|
"""
|
||||||
Path end-cap
|
Path end-cap
|
||||||
|
|
||||||
:return: Path.Cap enum
|
|
||||||
"""
|
"""
|
||||||
return self._cap
|
return self._cap
|
||||||
|
|
||||||
@ -74,9 +70,10 @@ class Path(Shape):
|
|||||||
@property
|
@property
|
||||||
def cap_extensions(self) -> numpy.ndarray or None:
|
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
|
return self._cap_extensions
|
||||||
|
|
||||||
@ -96,9 +93,7 @@ class Path(Shape):
|
|||||||
@property
|
@property
|
||||||
def vertices(self) -> numpy.ndarray:
|
def vertices(self) -> numpy.ndarray:
|
||||||
"""
|
"""
|
||||||
Vertices of the path (Nx2 ndarray: [[x0, y0], [x1, y1], ...]
|
Vertices of the path (Nx2 ndarray: `[[x0, y0], [x1, y1], ...]`)
|
||||||
|
|
||||||
:return: vertices
|
|
||||||
"""
|
"""
|
||||||
return self._vertices
|
return self._vertices
|
||||||
|
|
||||||
@ -194,22 +189,25 @@ class Path(Shape):
|
|||||||
Build a path by specifying the turn angles and travel distances
|
Build a path by specifying the turn angles and travel distances
|
||||||
rather than setting the distances directly.
|
rather than setting the distances directly.
|
||||||
|
|
||||||
:param travel_pairs: A list of (angle, distance) pairs that define
|
Args:
|
||||||
|
travel_pairs: A list of (angle, distance) pairs that define
|
||||||
the path. Angles are counterclockwise, in radians, and are relative
|
the path. Angles are counterclockwise, in radians, and are relative
|
||||||
to the previous segment's direction (the initial angle is relative
|
to the previous segment's direction (the initial angle is relative
|
||||||
to the +x axis).
|
to the +x axis).
|
||||||
:param width: Path width, default 0
|
width: Path width, default `0`
|
||||||
:param cap: End-cap type, default Path.Cap.Flush (no end-cap)
|
cap: End-cap type, default `Path.Cap.Flush` (no end-cap)
|
||||||
:param cap_extensions: End-cap extension distances, when using Path.Cap.CustomSquare.
|
cap_extensions: End-cap extension distances, when using `Path.Cap.CustomSquare`.
|
||||||
Default (0, 0) or None, depending on cap type
|
Default `(0, 0)` or `None`, depending on cap type
|
||||||
:param offset: Offset, default (0, 0)
|
offset: Offset, default `(0, 0)`
|
||||||
:param rotation: Rotation counterclockwise, in radians. Default 0
|
rotation: Rotation counterclockwise, in radians. Default `0`
|
||||||
:param mirrored: Whether to mirror across the x or y axes. For example,
|
mirrored: Whether to mirror across the x or y axes. For example,
|
||||||
mirrored=(True, False) results in a reflection across the x-axis,
|
`mirrored=(True, False)` results in a reflection across the x-axis,
|
||||||
multiplying the path's y-coordinates by -1. Default (False, False)
|
multiplying the path's y-coordinates by -1. Default `(False, False)`
|
||||||
:param layer: Layer, default 0
|
layer: Layer, default `0`
|
||||||
:param dose: Dose, default 1.0
|
dose: Dose, default `1.0`
|
||||||
:return: The resulting Path object
|
|
||||||
|
Returns:
|
||||||
|
The resulting Path object
|
||||||
"""
|
"""
|
||||||
#TODO: needs testing
|
#TODO: needs testing
|
||||||
direction = numpy.array([1, 0])
|
direction = numpy.array([1, 0])
|
||||||
@ -359,7 +357,8 @@ class Path(Shape):
|
|||||||
"""
|
"""
|
||||||
Removes duplicate, co-linear and otherwise redundant vertices.
|
Removes duplicate, co-linear and otherwise redundant vertices.
|
||||||
|
|
||||||
:returns: self
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
self.remove_colinear_vertices()
|
self.remove_colinear_vertices()
|
||||||
return self
|
return self
|
||||||
@ -368,7 +367,8 @@ class Path(Shape):
|
|||||||
'''
|
'''
|
||||||
Removes all consecutive duplicate (repeated) vertices.
|
Removes all consecutive duplicate (repeated) vertices.
|
||||||
|
|
||||||
:returns: self
|
Returns:
|
||||||
|
self
|
||||||
'''
|
'''
|
||||||
self.vertices = remove_duplicate_vertices(self.vertices, closed_path=False)
|
self.vertices = remove_duplicate_vertices(self.vertices, closed_path=False)
|
||||||
return self
|
return self
|
||||||
@ -377,7 +377,8 @@ class Path(Shape):
|
|||||||
'''
|
'''
|
||||||
Removes consecutive co-linear vertices.
|
Removes consecutive co-linear vertices.
|
||||||
|
|
||||||
:returns: self
|
Returns:
|
||||||
|
self
|
||||||
'''
|
'''
|
||||||
self.vertices = remove_colinear_vertices(self.vertices, closed_path=False)
|
self.vertices = remove_colinear_vertices(self.vertices, closed_path=False)
|
||||||
return self
|
return self
|
||||||
|
@ -16,18 +16,17 @@ class Polygon(Shape):
|
|||||||
A polygon, consisting of a bunch of vertices (Nx2 ndarray) which specify an
|
A polygon, consisting of a bunch of vertices (Nx2 ndarray) which specify an
|
||||||
implicitly-closed boundary, and an offset.
|
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',)
|
__slots__ = ('_vertices',)
|
||||||
_vertices: numpy.ndarray
|
_vertices: numpy.ndarray
|
||||||
|
""" Nx2 ndarray of vertices `[[x0, y0], [x1, y1], ...]` """
|
||||||
|
|
||||||
# vertices property
|
# vertices property
|
||||||
@property
|
@property
|
||||||
def vertices(self) -> numpy.ndarray:
|
def vertices(self) -> numpy.ndarray:
|
||||||
"""
|
"""
|
||||||
Vertices of the polygon (Nx2 ndarray: [[x0, y0], [x1, y1], ...]
|
Vertices of the polygon (Nx2 ndarray: `[[x0, y0], [x1, y1], ...]`)
|
||||||
|
|
||||||
:return: vertices
|
|
||||||
"""
|
"""
|
||||||
return self._vertices
|
return self._vertices
|
||||||
|
|
||||||
@ -107,12 +106,15 @@ class Polygon(Shape):
|
|||||||
"""
|
"""
|
||||||
Draw a square given side_length, centered on the origin.
|
Draw a square given side_length, centered on the origin.
|
||||||
|
|
||||||
:param side_length: Length of one side
|
Args:
|
||||||
:param rotation: Rotation counterclockwise, in radians
|
side_length: Length of one side
|
||||||
:param offset: Offset, default (0, 0)
|
rotation: Rotation counterclockwise, in radians
|
||||||
:param layer: Layer, default 0
|
offset: Offset, default `(0, 0)`
|
||||||
:param dose: Dose, default 1.0
|
layer: Layer, default `0`
|
||||||
:return: A Polygon object containing the requested square
|
dose: Dose, default `1.0`
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A Polygon object containing the requested square
|
||||||
"""
|
"""
|
||||||
norm_square = numpy.array([[-1, -1],
|
norm_square = numpy.array([[-1, -1],
|
||||||
[-1, +1],
|
[-1, +1],
|
||||||
@ -134,13 +136,16 @@ class Polygon(Shape):
|
|||||||
"""
|
"""
|
||||||
Draw a rectangle with side lengths lx and ly, centered on the origin.
|
Draw a rectangle with side lengths lx and ly, centered on the origin.
|
||||||
|
|
||||||
:param lx: Length along x (before rotation)
|
Args:
|
||||||
:param ly: Length along y (before rotation)
|
lx: Length along x (before rotation)
|
||||||
:param rotation: Rotation counterclockwise, in radians
|
ly: Length along y (before rotation)
|
||||||
:param offset: Offset, default (0, 0)
|
rotation: Rotation counterclockwise, in radians
|
||||||
:param layer: Layer, default 0
|
offset: Offset, default `(0, 0)`
|
||||||
:param dose: Dose, default 1.0
|
layer: Layer, default `0`
|
||||||
:return: A Polygon object containing the requested rectangle
|
dose: Dose, default `1.0`
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A Polygon object containing the requested rectangle
|
||||||
"""
|
"""
|
||||||
vertices = 0.5 * numpy.array([[-lx, -ly],
|
vertices = 0.5 * numpy.array([[-lx, -ly],
|
||||||
[-lx, +ly],
|
[-lx, +ly],
|
||||||
@ -168,17 +173,20 @@ class Polygon(Shape):
|
|||||||
Must provide 2 of (xmin, xctr, xmax, lx),
|
Must provide 2 of (xmin, xctr, xmax, lx),
|
||||||
and 2 of (ymin, yctr, ymax, ly).
|
and 2 of (ymin, yctr, ymax, ly).
|
||||||
|
|
||||||
:param xmin: Minimum x coordinate
|
Args:
|
||||||
:param xctr: Center x coordinate
|
xmin: Minimum x coordinate
|
||||||
:param xmax: Maximum x coordinate
|
xctr: Center x coordinate
|
||||||
:param lx: Length along x direction
|
xmax: Maximum x coordinate
|
||||||
:param ymin: Minimum y coordinate
|
lx: Length along x direction
|
||||||
:param yctr: Center y coordinate
|
ymin: Minimum y coordinate
|
||||||
:param ymax: Maximum y coordinate
|
yctr: Center y coordinate
|
||||||
:param ly: Length along y direction
|
ymax: Maximum y coordinate
|
||||||
:param layer: Layer, default 0
|
ly: Length along y direction
|
||||||
:param dose: Dose, default 1.0
|
layer: Layer, default `0`
|
||||||
:return: A Polygon object containing the requested rectangle
|
dose: Dose, default `1.0`
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A Polygon object containing the requested rectangle
|
||||||
"""
|
"""
|
||||||
if lx is None:
|
if lx is None:
|
||||||
if xctr is None:
|
if xctr is None:
|
||||||
@ -278,7 +286,8 @@ class Polygon(Shape):
|
|||||||
"""
|
"""
|
||||||
Removes duplicate, co-linear and otherwise redundant vertices.
|
Removes duplicate, co-linear and otherwise redundant vertices.
|
||||||
|
|
||||||
:returns: self
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
self.remove_colinear_vertices()
|
self.remove_colinear_vertices()
|
||||||
return self
|
return self
|
||||||
@ -287,7 +296,8 @@ class Polygon(Shape):
|
|||||||
'''
|
'''
|
||||||
Removes all consecutive duplicate (repeated) vertices.
|
Removes all consecutive duplicate (repeated) vertices.
|
||||||
|
|
||||||
:returns: self
|
Returns:
|
||||||
|
self
|
||||||
'''
|
'''
|
||||||
self.vertices = remove_duplicate_vertices(self.vertices, closed_path=True)
|
self.vertices = remove_duplicate_vertices(self.vertices, closed_path=True)
|
||||||
return self
|
return self
|
||||||
@ -296,7 +306,8 @@ class Polygon(Shape):
|
|||||||
'''
|
'''
|
||||||
Removes consecutive co-linear vertices.
|
Removes consecutive co-linear vertices.
|
||||||
|
|
||||||
:returns: self
|
Returns:
|
||||||
|
self
|
||||||
'''
|
'''
|
||||||
self.vertices = remove_colinear_vertices(self.vertices, closed_path=True)
|
self.vertices = remove_colinear_vertices(self.vertices, closed_path=True)
|
||||||
return self
|
return self
|
||||||
|
@ -26,12 +26,20 @@ class Shape(metaclass=ABCMeta):
|
|||||||
"""
|
"""
|
||||||
__slots__ = ('_offset', '_layer', '_dose', 'identifier', 'locked')
|
__slots__ = ('_offset', '_layer', '_dose', 'identifier', 'locked')
|
||||||
|
|
||||||
_offset: numpy.ndarray # [x_offset, y_offset]
|
_offset: numpy.ndarray
|
||||||
_layer: int or Tuple # Layer (integer >= 0 or tuple)
|
""" `[x_offset, y_offset]` """
|
||||||
_dose: float # Dose
|
|
||||||
identifier: Tuple # An arbitrary identifier for the shape,
|
_layer: int or Tuple
|
||||||
# usually empty but used by Pattern.flatten()
|
""" Layer (integer >= 0 or tuple) """
|
||||||
locked: bool # If True, any changes to the shape will raise a PatternLockedError
|
|
||||||
|
_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):
|
def __setattr__(self, name, value):
|
||||||
if self.locked and name != 'locked':
|
if self.locked and name != 'locked':
|
||||||
@ -51,31 +59,35 @@ class Shape(metaclass=ABCMeta):
|
|||||||
"""
|
"""
|
||||||
Returns a list of polygons which approximate the shape.
|
Returns a list of polygons which approximate the shape.
|
||||||
|
|
||||||
:param num_vertices: Number of points to use for each polygon. Can be overridden by
|
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'
|
max_arclen if that results in more points. Optional, defaults to shapes'
|
||||||
internal defaults.
|
internal defaults.
|
||||||
:param max_arclen: Maximum arclength which can be approximated by a single line
|
max_arclen: Maximum arclength which can be approximated by a single line
|
||||||
segment. Optional, defaults to shapes' internal defaults.
|
segment. Optional, defaults to shapes' internal defaults.
|
||||||
:return: List of polygons equivalent to the shape
|
|
||||||
|
Returns:
|
||||||
|
List of polygons equivalent to the shape
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_bounds(self) -> numpy.ndarray:
|
def get_bounds(self) -> numpy.ndarray:
|
||||||
"""
|
"""
|
||||||
Returns [[x_min, y_min], [x_max, y_max]] which specify a minimal bounding box for the shape.
|
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
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def rotate(self, theta: float) -> 'Shape':
|
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)
|
Args:
|
||||||
:return: self
|
theta: Angle to rotate by (counterclockwise, radians)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -84,8 +96,12 @@ class Shape(metaclass=ABCMeta):
|
|||||||
"""
|
"""
|
||||||
Mirror the shape across an axis.
|
Mirror the shape across an axis.
|
||||||
|
|
||||||
:param axis: Axis to mirror across.
|
Args:
|
||||||
:return: self
|
axis: Axis to mirror across.
|
||||||
|
(0: mirror across x axis, 1: mirror across y axis)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -94,8 +110,11 @@ class Shape(metaclass=ABCMeta):
|
|||||||
"""
|
"""
|
||||||
Scale the shape's size (eg. radius, for a circle) by a constant factor.
|
Scale the shape's size (eg. radius, for a circle) by a constant factor.
|
||||||
|
|
||||||
:param c: Factor to scale by
|
Args:
|
||||||
:return: self
|
c: Factor to scale by
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -105,18 +124,21 @@ 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 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
|
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,
|
|
||||||
(intrinsic, extrinsic, constructor). These are further broken down as:
|
Returns:
|
||||||
intrinsic: A tuple of basic types containing all information about the instance that
|
The returned information takes the form of a 3-element tuple,
|
||||||
is not contained in 'extrinsic'. Usually, intrinsic[0] == type(self).
|
`(intrinsic, extrinsic, constructor)`. These are further broken down as:
|
||||||
extrinsic: ([x_offset, y_offset], scale, rotation, mirror_across_x_axis, dose)
|
`intrinsic`: A tuple of basic types containing all information about the instance that
|
||||||
constructor: A callable (no arguments) which returns an instance of type(self) with
|
is not contained in 'extrinsic'. Usually, `intrinsic[0] == type(self)`.
|
||||||
internal state equivalent to 'intrinsic'.
|
`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
|
pass
|
||||||
|
|
||||||
@ -126,8 +148,6 @@ class Shape(metaclass=ABCMeta):
|
|||||||
def offset(self) -> numpy.ndarray:
|
def offset(self) -> numpy.ndarray:
|
||||||
"""
|
"""
|
||||||
[x, y] offset
|
[x, y] offset
|
||||||
|
|
||||||
:return: [x_offset, y_offset]
|
|
||||||
"""
|
"""
|
||||||
return self._offset
|
return self._offset
|
||||||
|
|
||||||
@ -145,8 +165,6 @@ class Shape(metaclass=ABCMeta):
|
|||||||
def layer(self) -> int or Tuple[int]:
|
def layer(self) -> int or Tuple[int]:
|
||||||
"""
|
"""
|
||||||
Layer number (int or tuple of ints)
|
Layer number (int or tuple of ints)
|
||||||
|
|
||||||
:return: Layer
|
|
||||||
"""
|
"""
|
||||||
return self._layer
|
return self._layer
|
||||||
|
|
||||||
@ -159,8 +177,6 @@ class Shape(metaclass=ABCMeta):
|
|||||||
def dose(self) -> float:
|
def dose(self) -> float:
|
||||||
"""
|
"""
|
||||||
Dose (float >= 0)
|
Dose (float >= 0)
|
||||||
|
|
||||||
:return: Dose value
|
|
||||||
"""
|
"""
|
||||||
return self._dose
|
return self._dose
|
||||||
|
|
||||||
@ -177,7 +193,8 @@ class Shape(metaclass=ABCMeta):
|
|||||||
"""
|
"""
|
||||||
Returns a deep copy of the shape.
|
Returns a deep copy of the shape.
|
||||||
|
|
||||||
:return: Deep copy of self
|
Returns:
|
||||||
|
copy.deepcopy(self)
|
||||||
"""
|
"""
|
||||||
return copy.deepcopy(self)
|
return copy.deepcopy(self)
|
||||||
|
|
||||||
@ -185,8 +202,11 @@ class Shape(metaclass=ABCMeta):
|
|||||||
"""
|
"""
|
||||||
Translate the shape by the given offset
|
Translate the shape by the given offset
|
||||||
|
|
||||||
:param offset: [x_offset, y,offset]
|
Args:
|
||||||
:return: self
|
offset: [x_offset, y,offset]
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
self.offset += offset
|
self.offset += offset
|
||||||
return self
|
return self
|
||||||
@ -195,9 +215,12 @@ class Shape(metaclass=ABCMeta):
|
|||||||
"""
|
"""
|
||||||
Rotate the shape around a point.
|
Rotate the shape around a point.
|
||||||
|
|
||||||
:param pivot: Point (x, y) to rotate around
|
Args:
|
||||||
:param rotation: Angle to rotate by (counterclockwise, radians)
|
pivot: Point (x, y) to rotate around
|
||||||
:return: self
|
rotation: Angle to rotate by (counterclockwise, radians)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
pivot = numpy.array(pivot, dtype=float)
|
pivot = numpy.array(pivot, dtype=float)
|
||||||
self.translate(-pivot)
|
self.translate(-pivot)
|
||||||
@ -214,14 +237,17 @@ class Shape(metaclass=ABCMeta):
|
|||||||
Returns a list of polygons with grid-aligned ("Manhattan") edges approximating the shape.
|
Returns a list of polygons with grid-aligned ("Manhattan") edges approximating the shape.
|
||||||
|
|
||||||
This function works by
|
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
|
2) Approximating each edge with an equivalent Manhattan edge
|
||||||
This process results in a reasonable Manhattan representation of the shape, but is
|
This process results in a reasonable Manhattan representation of the shape, but is
|
||||||
imprecise near non-Manhattan or off-grid corners.
|
imprecise near non-Manhattan or off-grid corners.
|
||||||
|
|
||||||
:param grid_x: List of allowed x-coordinates for the Manhattanized polygon edges.
|
Args:
|
||||||
:param grid_y: List of allowed y-coordinates for the Manhattanized polygon edges.
|
grid_x: List of allowed x-coordinates for the Manhattanized polygon edges.
|
||||||
:return: List of Polygon objects with grid-aligned 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
|
from . import Polygon
|
||||||
|
|
||||||
@ -319,7 +345,7 @@ class Shape(metaclass=ABCMeta):
|
|||||||
Returns a list of polygons with grid-aligned ("Manhattan") edges approximating the shape.
|
Returns a list of polygons with grid-aligned ("Manhattan") edges approximating the shape.
|
||||||
|
|
||||||
This function works by
|
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,
|
2) Accurately rasterizing each polygon on a grid,
|
||||||
where the edges of each grid cell correspond to the allowed coordinates
|
where the edges of each grid cell correspond to the allowed coordinates
|
||||||
3) Thresholding the (anti-aliased) rasterized image
|
3) Thresholding the (anti-aliased) rasterized image
|
||||||
@ -328,7 +354,7 @@ class Shape(metaclass=ABCMeta):
|
|||||||
caveats include:
|
caveats include:
|
||||||
a) If high accuracy is important, perform any polygonization and clipping operations
|
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
|
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).
|
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
|
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.
|
by breaking the shape apart into multiple, smaller shapes.
|
||||||
@ -336,19 +362,22 @@ class Shape(metaclass=ABCMeta):
|
|||||||
equidistant from allowed edge location.
|
equidistant from allowed edge location.
|
||||||
|
|
||||||
Implementation notes:
|
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.
|
rasterized image.
|
||||||
ii) To find the exact polygon edges, the thresholded rasterized image is supersampled
|
ii) To find the exact polygon edges, the thresholded rasterized image is supersampled
|
||||||
prior to calling skimage.measure.find_contours(), which uses marching squares
|
prior to calling `skimage.measure.find_contours()`, which uses marching squares
|
||||||
to find the contours. This is done because find_contours() performs interpolation,
|
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
|
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
|
boundary tracing method could remove this requirement, but for now this seems to
|
||||||
be the most performant approach.
|
be the most performant approach.
|
||||||
|
|
||||||
:param grid_x: List of allowed x-coordinates for the Manhattanized polygon edges.
|
Args:
|
||||||
:param grid_y: List of allowed y-coordinates for the Manhattanized polygon edges.
|
grid_x: List of allowed x-coordinates for the Manhattanized polygon edges.
|
||||||
:return: List of Polygon objects with grid-aligned 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
|
from . import Polygon
|
||||||
import skimage.measure
|
import skimage.measure
|
||||||
@ -403,9 +432,10 @@ class Shape(metaclass=ABCMeta):
|
|||||||
|
|
||||||
def lock(self) -> 'Shape':
|
def lock(self) -> 'Shape':
|
||||||
"""
|
"""
|
||||||
Lock the Shape
|
Lock the Shape, disallowing further changes
|
||||||
|
|
||||||
:return: self
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
object.__setattr__(self, 'locked', True)
|
object.__setattr__(self, 'locked', True)
|
||||||
return self
|
return self
|
||||||
@ -414,7 +444,8 @@ class Shape(metaclass=ABCMeta):
|
|||||||
"""
|
"""
|
||||||
Unlock the Shape
|
Unlock the Shape
|
||||||
|
|
||||||
:return: self
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
object.__setattr__(self, 'locked', False)
|
object.__setattr__(self, 'locked', False)
|
||||||
return self
|
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.
|
The output is normalized so that the font size is 1 unit.
|
||||||
|
|
||||||
:param font_path: File path specifying a font loadable by freetype
|
Args:
|
||||||
:param char: Character to convert to polygons
|
font_path: File path specifying a font loadable by freetype
|
||||||
:param resolution: Internal resolution setting (used for freetype
|
char: Character to convert to polygons
|
||||||
Face.set_font_size(resolution)). Modify at your own peril!
|
resolution: Internal resolution setting (used for freetype
|
||||||
:return: List of polygons [[[x0, y0], [x1, y1], ...], ...] and 'advance' distance (distance
|
`Face.set_font_size(resolution))`. Modify at your own peril!
|
||||||
from the start of this glyph to the start of the next one)
|
|
||||||
|
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:
|
if len(char) != 1:
|
||||||
raise Exception('get_char_as_polygons called with non-char')
|
raise Exception('get_char_as_polygons called with non-char')
|
||||||
|
@ -24,13 +24,29 @@ class SubPattern:
|
|||||||
__slots__ = ('pattern', '_offset', '_rotation', '_dose', '_scale', '_mirrored',
|
__slots__ = ('pattern', '_offset', '_rotation', '_dose', '_scale', '_mirrored',
|
||||||
'identifier', 'locked')
|
'identifier', 'locked')
|
||||||
pattern: 'Pattern'
|
pattern: 'Pattern'
|
||||||
|
""" The `Pattern` being instanced """
|
||||||
|
|
||||||
_offset: numpy.ndarray
|
_offset: numpy.ndarray
|
||||||
|
""" (x, y) offset for the instance """
|
||||||
|
|
||||||
_rotation: float
|
_rotation: float
|
||||||
|
""" rotation for the instance, radians counterclockwise """
|
||||||
|
|
||||||
_dose: float
|
_dose: float
|
||||||
|
""" dose factor for the instance """
|
||||||
|
|
||||||
_scale: float
|
_scale: float
|
||||||
|
""" scale factor for the instance """
|
||||||
|
|
||||||
_mirrored: List[bool]
|
_mirrored: List[bool]
|
||||||
|
""" Whether to mirror the instanc across the x and/or y axes. """
|
||||||
|
|
||||||
identifier: Tuple
|
identifier: Tuple
|
||||||
|
""" An arbitrary identifier """
|
||||||
|
|
||||||
locked: bool
|
locked: bool
|
||||||
|
""" If `True`, disallows changes to the GridRepetition """
|
||||||
|
|
||||||
|
|
||||||
#TODO more documentation?
|
#TODO more documentation?
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
@ -139,9 +155,9 @@ class SubPattern:
|
|||||||
|
|
||||||
def as_pattern(self) -> 'Pattern':
|
def as_pattern(self) -> 'Pattern':
|
||||||
"""
|
"""
|
||||||
Returns a copy of self.pattern which has been scaled, rotated, etc. according to this
|
Returns:
|
||||||
SubPattern's properties.
|
A copy of self.pattern which has been scaled, rotated, etc. according to this
|
||||||
:return: Copy of self.pattern that has been altered to reflect the SubPattern's properties.
|
`SubPattern`'s properties.
|
||||||
"""
|
"""
|
||||||
pattern = self.pattern.deepcopy().deepunlock()
|
pattern = self.pattern.deepcopy().deepunlock()
|
||||||
pattern.scale_by(self.scale)
|
pattern.scale_by(self.scale)
|
||||||
@ -155,8 +171,11 @@ class SubPattern:
|
|||||||
"""
|
"""
|
||||||
Translate by the given offset
|
Translate by the given offset
|
||||||
|
|
||||||
:param offset: Translate by this offset
|
Args:
|
||||||
:return: self
|
offset: Offset `[x, y]` to translate by
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
self.offset += offset
|
self.offset += offset
|
||||||
return self
|
return self
|
||||||
@ -165,9 +184,12 @@ class SubPattern:
|
|||||||
"""
|
"""
|
||||||
Rotate around a point
|
Rotate around a point
|
||||||
|
|
||||||
:param pivot: Point to rotate around
|
Args:
|
||||||
:param rotation: Angle to rotate by (counterclockwise, radians)
|
pivot: Point `[x, y]` to rotate around
|
||||||
:return: self
|
rotation: Angle to rotate by (counterclockwise, radians)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
pivot = numpy.array(pivot, dtype=float)
|
pivot = numpy.array(pivot, dtype=float)
|
||||||
self.translate(-pivot)
|
self.translate(-pivot)
|
||||||
@ -178,10 +200,13 @@ class SubPattern:
|
|||||||
|
|
||||||
def rotate(self, rotation: float) -> 'SubPattern':
|
def rotate(self, rotation: float) -> 'SubPattern':
|
||||||
"""
|
"""
|
||||||
Rotate around (0, 0)
|
Rotate the instance around it's origin
|
||||||
|
|
||||||
:param rotation: Angle to rotate by (counterclockwise, radians)
|
Args:
|
||||||
:return: self
|
rotation: Angle to rotate by (counterclockwise, radians)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
self.rotation += rotation
|
self.rotation += rotation
|
||||||
return self
|
return self
|
||||||
@ -190,8 +215,11 @@ class SubPattern:
|
|||||||
"""
|
"""
|
||||||
Mirror the subpattern across an axis.
|
Mirror the subpattern across an axis.
|
||||||
|
|
||||||
:param axis: Axis to mirror across.
|
Args:
|
||||||
:return: self
|
axis: Axis to mirror across.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
self.mirrored[axis] = not self.mirrored[axis]
|
self.mirrored[axis] = not self.mirrored[axis]
|
||||||
self.rotation *= -1
|
self.rotation *= -1
|
||||||
@ -199,11 +227,12 @@ class SubPattern:
|
|||||||
|
|
||||||
def get_bounds(self) -> numpy.ndarray or None:
|
def get_bounds(self) -> numpy.ndarray or None:
|
||||||
"""
|
"""
|
||||||
Return a numpy.ndarray containing [[x_min, y_min], [x_max, y_max]], corresponding to the
|
Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the
|
||||||
extent of the SubPattern in each dimension.
|
extent of the `SubPattern` in each dimension.
|
||||||
Returns None if the contained Pattern is empty.
|
Returns `None` if the contained `Pattern` is empty.
|
||||||
|
|
||||||
:return: [[x_min, y_min], [x_max, y_max]] or None
|
Returns:
|
||||||
|
`[[x_min, y_min], [x_max, y_max]]` or `None`
|
||||||
"""
|
"""
|
||||||
return self.as_pattern().get_bounds()
|
return self.as_pattern().get_bounds()
|
||||||
|
|
||||||
@ -211,7 +240,11 @@ class SubPattern:
|
|||||||
"""
|
"""
|
||||||
Scale the subpattern by a factor
|
Scale the subpattern by a factor
|
||||||
|
|
||||||
:param c: scaling factor
|
Args:
|
||||||
|
c: scaling factor
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
self.scale *= c
|
self.scale *= c
|
||||||
return self
|
return self
|
||||||
@ -220,7 +253,8 @@ class SubPattern:
|
|||||||
"""
|
"""
|
||||||
Return a shallow copy of the subpattern.
|
Return a shallow copy of the subpattern.
|
||||||
|
|
||||||
:return: copy.copy(self)
|
Returns:
|
||||||
|
`copy.copy(self)`
|
||||||
"""
|
"""
|
||||||
return copy.copy(self)
|
return copy.copy(self)
|
||||||
|
|
||||||
@ -228,15 +262,17 @@ class SubPattern:
|
|||||||
"""
|
"""
|
||||||
Return a deep copy of the subpattern.
|
Return a deep copy of the subpattern.
|
||||||
|
|
||||||
:return: copy.copy(self)
|
Returns:
|
||||||
|
`copy.deepcopy(self)`
|
||||||
"""
|
"""
|
||||||
return copy.deepcopy(self)
|
return copy.deepcopy(self)
|
||||||
|
|
||||||
def lock(self) -> 'SubPattern':
|
def lock(self) -> 'SubPattern':
|
||||||
"""
|
"""
|
||||||
Lock the SubPattern
|
Lock the SubPattern, disallowing changes
|
||||||
|
|
||||||
:return: self
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
object.__setattr__(self, 'locked', True)
|
object.__setattr__(self, 'locked', True)
|
||||||
return self
|
return self
|
||||||
@ -245,7 +281,8 @@ class SubPattern:
|
|||||||
"""
|
"""
|
||||||
Unlock the SubPattern
|
Unlock the SubPattern
|
||||||
|
|
||||||
:return: self
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
object.__setattr__(self, 'locked', False)
|
object.__setattr__(self, 'locked', False)
|
||||||
return self
|
return self
|
||||||
@ -254,7 +291,8 @@ class SubPattern:
|
|||||||
"""
|
"""
|
||||||
Recursively lock the SubPattern and its contained pattern
|
Recursively lock the SubPattern and its contained pattern
|
||||||
|
|
||||||
:return: self
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
self.lock()
|
self.lock()
|
||||||
self.pattern.deeplock()
|
self.pattern.deeplock()
|
||||||
@ -264,9 +302,11 @@ class SubPattern:
|
|||||||
"""
|
"""
|
||||||
Recursively unlock the SubPattern and its contained pattern
|
Recursively unlock the SubPattern and its contained pattern
|
||||||
|
|
||||||
This is dangerous unless you have just performed a deepcopy!
|
This is dangerous unless you have just performed a deepcopy, since
|
||||||
|
the subpattern and its components may be used in more than one once!
|
||||||
|
|
||||||
:return: self
|
Returns:
|
||||||
|
self
|
||||||
"""
|
"""
|
||||||
self.unlock()
|
self.unlock()
|
||||||
self.pattern.deepunlock()
|
self.pattern.deepunlock()
|
||||||
|
@ -14,30 +14,37 @@ def is_scalar(var: Any) -> bool:
|
|||||||
"""
|
"""
|
||||||
Alias for 'not hasattr(var, "__len__")'
|
Alias for 'not hasattr(var, "__len__")'
|
||||||
|
|
||||||
:param var: Checks if var has a length.
|
Args:
|
||||||
|
var: Checks if `var` has a length.
|
||||||
"""
|
"""
|
||||||
return not hasattr(var, "__len__")
|
return not hasattr(var, "__len__")
|
||||||
|
|
||||||
|
|
||||||
def get_bit(bit_string: Any, bit_id: int) -> bool:
|
def get_bit(bit_string: Any, bit_id: int) -> bool:
|
||||||
"""
|
"""
|
||||||
Returns true iff bit number 'bit_id' from the right of 'bit_string' is 1
|
Interprets bit number `bit_id` from the right (lsb) of `bit_string` as a boolean
|
||||||
|
|
||||||
:param bit_string: Bit string to test
|
Args:
|
||||||
:param bit_id: Bit number, 0-indexed from the right (lsb)
|
bit_string: Bit string to test
|
||||||
:return: value of the requested bit (bool)
|
bit_id: Bit number, 0-indexed from the right (lsb)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Boolean value of the requested bit
|
||||||
"""
|
"""
|
||||||
return bit_string & (1 << bit_id) != 0
|
return bit_string & (1 << bit_id) != 0
|
||||||
|
|
||||||
|
|
||||||
def set_bit(bit_string: Any, bit_id: int, value: bool) -> Any:
|
def set_bit(bit_string: Any, bit_id: int, value: bool) -> Any:
|
||||||
"""
|
"""
|
||||||
Returns 'bit_string' with bit number 'bit_id' set to 'value'.
|
Returns `bit_string`, with bit number `bit_id` set to boolean `value`.
|
||||||
|
|
||||||
:param bit_string: Bit string to alter
|
Args:
|
||||||
:param bit_id: Bit number, 0-indexed from right (lsb)
|
bit_string: Bit string to alter
|
||||||
:param value: Boolean value to set bit to
|
bit_id: Bit number, 0-indexed from right (lsb)
|
||||||
:return: Altered 'bit_string'
|
value: Boolean value to set bit to
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Altered `bit_string`
|
||||||
"""
|
"""
|
||||||
mask = (1 << bit_id)
|
mask = (1 << bit_id)
|
||||||
bit_string &= ~mask
|
bit_string &= ~mask
|
||||||
@ -50,14 +57,29 @@ def rotation_matrix_2d(theta: float) -> numpy.ndarray:
|
|||||||
"""
|
"""
|
||||||
2D rotation matrix for rotating counterclockwise around the origin.
|
2D rotation matrix for rotating counterclockwise around the origin.
|
||||||
|
|
||||||
:param theta: Angle to rotate, in radians
|
Args:
|
||||||
:return: rotation matrix
|
theta: Angle to rotate, in radians
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
rotation matrix
|
||||||
"""
|
"""
|
||||||
return numpy.array([[numpy.cos(theta), -numpy.sin(theta)],
|
return numpy.array([[numpy.cos(theta), -numpy.sin(theta)],
|
||||||
[numpy.sin(theta), +numpy.cos(theta)]])
|
[numpy.sin(theta), +numpy.cos(theta)]])
|
||||||
|
|
||||||
|
|
||||||
def normalize_mirror(mirrored: Tuple[bool, bool]) -> Tuple[bool, float]:
|
def normalize_mirror(mirrored: Tuple[bool, bool]) -> Tuple[bool, float]:
|
||||||
|
"""
|
||||||
|
Converts 0-2 mirror operations `(mirror_across_x_axis, mirror_across_y_axis)`
|
||||||
|
into 0-1 mirror operations and a rotation
|
||||||
|
|
||||||
|
Args:
|
||||||
|
mirrored: `(mirror_across_x_axis, mirror_across_y_axis)`
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
`mirror_across_x_axis` (bool) and
|
||||||
|
`angle_to_rotate` in radians
|
||||||
|
"""
|
||||||
|
|
||||||
mirrored_x, mirrored_y = mirrored
|
mirrored_x, mirrored_y = mirrored
|
||||||
mirror_x = (mirrored_x != mirrored_y) #XOR
|
mirror_x = (mirrored_x != mirrored_y) #XOR
|
||||||
angle = numpy.pi if mirrored_y else 0
|
angle = numpy.pi if mirrored_y else 0
|
||||||
@ -65,6 +87,17 @@ def normalize_mirror(mirrored: Tuple[bool, bool]) -> Tuple[bool, float]:
|
|||||||
|
|
||||||
|
|
||||||
def remove_duplicate_vertices(vertices: numpy.ndarray, closed_path: bool = True) -> numpy.ndarray:
|
def remove_duplicate_vertices(vertices: numpy.ndarray, closed_path: bool = True) -> numpy.ndarray:
|
||||||
|
"""
|
||||||
|
Given a list of vertices, remove any consecutive duplicates.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
vertices: `[[x0, y0], [x1, y1], ...]`
|
||||||
|
closed_path: If True, `vertices` is interpreted as an implicity-closed path
|
||||||
|
(i.e. the last vertex will be removed if it is the same as the first)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
`vertices` with no consecutive duplicates.
|
||||||
|
"""
|
||||||
duplicates = (vertices == numpy.roll(vertices, 1, axis=0)).all(axis=1)
|
duplicates = (vertices == numpy.roll(vertices, 1, axis=0)).all(axis=1)
|
||||||
if not closed_path:
|
if not closed_path:
|
||||||
duplicates[0] = False
|
duplicates[0] = False
|
||||||
@ -72,15 +105,18 @@ def remove_duplicate_vertices(vertices: numpy.ndarray, closed_path: bool = True)
|
|||||||
|
|
||||||
|
|
||||||
def remove_colinear_vertices(vertices: numpy.ndarray, closed_path: bool = True) -> numpy.ndarray:
|
def remove_colinear_vertices(vertices: numpy.ndarray, closed_path: bool = True) -> numpy.ndarray:
|
||||||
'''
|
"""
|
||||||
Given a list of vertices, remove any superflous vertices (i.e.
|
Given a list of vertices, remove any superflous vertices (i.e.
|
||||||
those which lie along the line formed by their neighbors)
|
those which lie along the line formed by their neighbors)
|
||||||
|
|
||||||
:param vertices: Nx2 ndarray of vertices
|
Args:
|
||||||
:param closed_path: If True, the vertices are assumed to represent an implicitly
|
vertices: Nx2 ndarray of vertices
|
||||||
closed path. If False, the path is assumed to be open. Default True.
|
closed_path: If `True`, the vertices are assumed to represent an implicitly
|
||||||
:return:
|
closed path. If `False`, the path is assumed to be open. Default `True`.
|
||||||
'''
|
|
||||||
|
Returns:
|
||||||
|
`vertices` with colinear (superflous) vertices removed.
|
||||||
|
"""
|
||||||
vertices = numpy.array(vertices)
|
vertices = numpy.array(vertices)
|
||||||
|
|
||||||
# Check for dx0/dy0 == dx1/dy1
|
# Check for dx0/dy0 == dx1/dy1
|
||||||
|
Loading…
Reference in New Issue
Block a user