Remove support for dose
Since there isn't GDS/OASIS level support for dose, this can be mostly handled by using arbitrary layers/dtypes directly. Dose scaling isn't handled as nicely that way, but it corresponds more directly to what gets written to file.
This commit is contained in:
parent
f7a2edfe23
commit
c7f3e7ee52
@ -3,8 +3,8 @@
|
|||||||
Masque is a Python module for designing lithography masks.
|
Masque is a Python module for designing lithography masks.
|
||||||
|
|
||||||
The general idea is to implement something resembling the GDSII file-format, but
|
The general idea is to implement something resembling the GDSII file-format, but
|
||||||
with some vectorized element types (eg. circles, not just polygons), better support for
|
with some vectorized element types (eg. circles, not just polygons) and the ability
|
||||||
E-beam doses, and the ability to output to multiple formats.
|
to output to multiple formats.
|
||||||
|
|
||||||
- [Source repository](https://mpxd.net/code/jan/masque)
|
- [Source repository](https://mpxd.net/code/jan/masque)
|
||||||
- [PyPI](https://pypi.org/project/masque)
|
- [PyPI](https://pypi.org/project/masque)
|
||||||
|
@ -32,14 +32,13 @@ def hole(layer: layer_t,
|
|||||||
Pattern, named `'hole'`
|
Pattern, named `'hole'`
|
||||||
"""
|
"""
|
||||||
pat = Pattern('hole', shapes=[
|
pat = Pattern('hole', shapes=[
|
||||||
Circle(radius=radius, offset=(0, 0), layer=layer, dose=1.0)
|
Circle(radius=radius, offset=(0, 0), layer=layer)
|
||||||
])
|
])
|
||||||
return pat
|
return pat
|
||||||
|
|
||||||
|
|
||||||
def perturbed_l3(lattice_constant: float,
|
def perturbed_l3(lattice_constant: float,
|
||||||
hole: Pattern,
|
hole: Pattern,
|
||||||
trench_dose: float = 1.0,
|
|
||||||
trench_layer: layer_t = (1, 0),
|
trench_layer: layer_t = (1, 0),
|
||||||
shifts_a: Sequence[float] = (0.15, 0, 0.075),
|
shifts_a: Sequence[float] = (0.15, 0, 0.075),
|
||||||
shifts_r: Sequence[float] = (1.0, 1.0, 1.0),
|
shifts_r: Sequence[float] = (1.0, 1.0, 1.0),
|
||||||
@ -53,7 +52,6 @@ def perturbed_l3(lattice_constant: float,
|
|||||||
Args:
|
Args:
|
||||||
lattice_constant: Distance between nearest neighbor holes
|
lattice_constant: Distance between nearest neighbor holes
|
||||||
hole: `Pattern` object containing a single hole
|
hole: `Pattern` object containing a single hole
|
||||||
trench_dose: Dose for the trenches. Default 1.0. (Hole dose is 1.0.)
|
|
||||||
trench_layer: Layer for the trenches, default `(1, 0)`.
|
trench_layer: Layer for the trenches, default `(1, 0)`.
|
||||||
shifts_a: passed to `pcgen.l3_shift`; specifies lattice constant
|
shifts_a: passed to `pcgen.l3_shift`; specifies lattice constant
|
||||||
(1 - multiplicative factor) for shifting holes adjacent to
|
(1 - multiplicative factor) for shifting holes adjacent to
|
||||||
@ -85,10 +83,8 @@ def perturbed_l3(lattice_constant: float,
|
|||||||
trench_dx = max_xy[0] - min_xy[0]
|
trench_dx = max_xy[0] - min_xy[0]
|
||||||
|
|
||||||
pat.shapes += [
|
pat.shapes += [
|
||||||
Polygon.rect(ymin=max_xy[1], xmin=min_xy[0], lx=trench_dx, ly=trench_width,
|
Polygon.rect(ymin=max_xy[1], xmin=min_xy[0], lx=trench_dx, ly=trench_width, layer=trench_layer),
|
||||||
layer=trench_layer, dose=trench_dose),
|
Polygon.rect(ymax=min_xy[1], xmin=min_xy[0], lx=trench_dx, ly=trench_width, layer=trench_layer),
|
||||||
Polygon.rect(ymax=min_xy[1], xmin=min_xy[0], lx=trench_dx, ly=trench_width,
|
|
||||||
layer=trench_layer, dose=trench_dose),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
ports = {
|
ports = {
|
||||||
|
@ -36,7 +36,6 @@ def pat2dev(pat: Pattern) -> Device:
|
|||||||
def perturbed_l3(
|
def perturbed_l3(
|
||||||
lattice_constant: float,
|
lattice_constant: float,
|
||||||
hole: Pattern,
|
hole: Pattern,
|
||||||
trench_dose: float = 1.0,
|
|
||||||
trench_layer: layer_t = (1, 0),
|
trench_layer: layer_t = (1, 0),
|
||||||
shifts_a: Sequence[float] = (0.15, 0, 0.075),
|
shifts_a: Sequence[float] = (0.15, 0, 0.075),
|
||||||
shifts_r: Sequence[float] = (1.0, 1.0, 1.0),
|
shifts_r: Sequence[float] = (1.0, 1.0, 1.0),
|
||||||
@ -50,7 +49,6 @@ def perturbed_l3(
|
|||||||
Args:
|
Args:
|
||||||
lattice_constant: Distance between nearest neighbor holes
|
lattice_constant: Distance between nearest neighbor holes
|
||||||
hole: `Pattern` object containing a single hole
|
hole: `Pattern` object containing a single hole
|
||||||
trench_dose: Dose for the trenches. Default 1.0. (Hole dose is 1.0.)
|
|
||||||
trench_layer: Layer for the trenches, default `(1, 0)`.
|
trench_layer: Layer for the trenches, default `(1, 0)`.
|
||||||
shifts_a: passed to `pcgen.l3_shift`; specifies lattice constant
|
shifts_a: passed to `pcgen.l3_shift`; specifies lattice constant
|
||||||
(1 - multiplicative factor) for shifting holes adjacent to
|
(1 - multiplicative factor) for shifting holes adjacent to
|
||||||
@ -87,10 +85,8 @@ def perturbed_l3(
|
|||||||
trench_dx = max_xy[0] - min_xy[0]
|
trench_dx = max_xy[0] - min_xy[0]
|
||||||
|
|
||||||
pat.shapes += [
|
pat.shapes += [
|
||||||
Polygon.rect(ymin=max_xy[1], xmin=min_xy[0], lx=trench_dx, ly=trench_width,
|
Polygon.rect(ymin=max_xy[1], xmin=min_xy[0], lx=trench_dx, ly=trench_width, layer=trench_layer),
|
||||||
layer=trench_layer, dose=trench_dose),
|
Polygon.rect(ymax=min_xy[1], xmin=min_xy[0], lx=trench_dx, ly=trench_width, layer=trench_layer),
|
||||||
Polygon.rect(ymax=min_xy[1], xmin=min_xy[0], lx=trench_dx, ly=trench_width,
|
|
||||||
layer=trench_layer, dose=trench_dose),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Ports are at outer extents of the device (with y=0)
|
# Ports are at outer extents of the device (with y=0)
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
masque is an attempt to make a relatively small library for designing lithography
|
masque is an attempt to make a relatively small library for designing lithography
|
||||||
masks. The general idea is to implement something resembling the GDSII and OASIS file-formats,
|
masks. The general idea is to implement something resembling the GDSII and OASIS file-formats,
|
||||||
but with some additional vectorized element types (eg. ellipses, not just polygons), better
|
but with some additional vectorized element types (eg. ellipses, not just polygons), and the
|
||||||
support for E-beam doses, and the ability to interface with multiple file formats.
|
ability to interface with multiple file 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, a list of `Label` objects, and a list of references to other `Patterns` (using
|
objects, a list of `Label` objects, and a list of references to other `Patterns` (using
|
||||||
|
@ -26,8 +26,8 @@ def writefile(
|
|||||||
|
|
||||||
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`, a non-standard `pattern_layer` attribute
|
||||||
are written to the relevant elements.
|
is 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
|
||||||
@ -39,8 +39,8 @@ def writefile(
|
|||||||
Args:
|
Args:
|
||||||
pattern: Pattern to write to file. Modified by this function.
|
pattern: Pattern to write to file. Modified by this function.
|
||||||
filename: Filename to write to.
|
filename: Filename to write to.
|
||||||
custom_attributes: Whether to write non-standard `pattern_layer` and
|
custom_attributes: Whether to write non-standard `pattern_layer` attribute to the
|
||||||
`pattern_dose` attributes to the SVG elements.
|
SVG elements.
|
||||||
"""
|
"""
|
||||||
pattern = library[top]
|
pattern = library[top]
|
||||||
|
|
||||||
@ -61,8 +61,7 @@ def writefile(
|
|||||||
svg = svgwrite.Drawing(filename, profile='full', viewBox=viewbox_string,
|
svg = svgwrite.Drawing(filename, profile='full', viewBox=viewbox_string,
|
||||||
debug=(not custom_attributes))
|
debug=(not custom_attributes))
|
||||||
|
|
||||||
# Now create a group for each row in sd_table (ie, each pattern + dose combination)
|
# Now create a group for each pattern and add in any Boundary and Use elements
|
||||||
# and add in any Boundary and Use elements
|
|
||||||
for name, pat in library.items():
|
for name, pat in library.items():
|
||||||
svg_group = svg.g(id=mangle_name(name), fill='blue', stroke='red')
|
svg_group = svg.g(id=mangle_name(name), fill='blue', stroke='red')
|
||||||
|
|
||||||
@ -73,7 +72,6 @@ def writefile(
|
|||||||
path = svg.path(d=path_spec)
|
path = svg.path(d=path_spec)
|
||||||
if custom_attributes:
|
if custom_attributes:
|
||||||
path['pattern_layer'] = polygon.layer
|
path['pattern_layer'] = polygon.layer
|
||||||
path['pattern_dose'] = polygon.dose
|
|
||||||
|
|
||||||
svg_group.add(path)
|
svg_group.add(path)
|
||||||
|
|
||||||
@ -82,8 +80,6 @@ def writefile(
|
|||||||
continue
|
continue
|
||||||
transform = f'scale({subpat.scale:g}) rotate({subpat.rotation:g}) translate({subpat.offset[0]:g},{subpat.offset[1]:g})'
|
transform = f'scale({subpat.scale:g}) rotate({subpat.rotation:g}) translate({subpat.offset[0]:g},{subpat.offset[1]:g})'
|
||||||
use = svg.use(href='#' + mangle_name(subpat.target), transform=transform)
|
use = svg.use(href='#' + mangle_name(subpat.target), transform=transform)
|
||||||
if custom_attributes:
|
|
||||||
use['pattern_dose'] = subpat.dose
|
|
||||||
svg_group.add(use)
|
svg_group.add(use)
|
||||||
|
|
||||||
svg.defs.add(svg_group)
|
svg.defs.add(svg_group)
|
||||||
|
@ -15,23 +15,18 @@ from ..shapes import Polygon, Path
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def mangle_name(name: str, dose_multiplier: float = 1.0) -> str:
|
def mangle_name(name: str) -> str:
|
||||||
"""
|
"""
|
||||||
Create a new name using `name` and the `dose_multiplier`.
|
Sanitize a name.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name: Name we want to mangle.
|
name: Name we want to mangle.
|
||||||
dose_multiplier: Dose multiplier to mangle with.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Mangled name.
|
Mangled name.
|
||||||
"""
|
"""
|
||||||
if dose_multiplier == 1:
|
|
||||||
full_name = name
|
|
||||||
else:
|
|
||||||
full_name = f'{name}_dm{dose_multiplier}'
|
|
||||||
expression = re.compile(r'[^A-Za-z0-9_\?\$]')
|
expression = re.compile(r'[^A-Za-z0-9_\?\$]')
|
||||||
sanitized_name = expression.sub('_', full_name)
|
sanitized_name = expression.sub('_', name)
|
||||||
return sanitized_name
|
return sanitized_name
|
||||||
|
|
||||||
|
|
||||||
@ -59,134 +54,6 @@ def clean_pattern_vertices(pat: Pattern) -> Pattern:
|
|||||||
return pat
|
return pat
|
||||||
|
|
||||||
|
|
||||||
def make_dose_table(
|
|
||||||
top_names: Iterable[str],
|
|
||||||
library: Mapping[str, Pattern],
|
|
||||||
dose_multiplier: float = 1.0,
|
|
||||||
) -> Set[Tuple[str, float]]:
|
|
||||||
"""
|
|
||||||
Create a set containing `(name, written_dose)` for each pattern (including subpatterns)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
top_names: Names of all topcells
|
|
||||||
pattern: Source Patterns.
|
|
||||||
dose_multiplier: Multiplier for all written_dose entries.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
`{(name, written_dose), ...}`
|
|
||||||
"""
|
|
||||||
dose_table = {(top_name, dose_multiplier) for top_name in top_names}
|
|
||||||
for name, pattern in library.items():
|
|
||||||
for subpat in pattern.subpatterns:
|
|
||||||
if subpat.target is None:
|
|
||||||
continue
|
|
||||||
subpat_dose_entry = (subpat.target, subpat.dose * dose_multiplier)
|
|
||||||
if subpat_dose_entry not in dose_table:
|
|
||||||
subpat_dose_table = make_dose_table(subpat.target, library, subpat.dose * dose_multiplier)
|
|
||||||
dose_table = dose_table.union(subpat_dose_table)
|
|
||||||
return dose_table
|
|
||||||
|
|
||||||
|
|
||||||
def dtype2dose(pattern: Pattern) -> Pattern:
|
|
||||||
"""
|
|
||||||
For each shape in the pattern, if the layer is a tuple, set the
|
|
||||||
layer to the tuple's first element and set the dose to the
|
|
||||||
tuple's second element.
|
|
||||||
|
|
||||||
Generally intended for use with `Pattern.apply()`.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
pattern: Pattern to modify
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
pattern
|
|
||||||
"""
|
|
||||||
for shape in pattern.shapes:
|
|
||||||
if isinstance(shape.layer, tuple):
|
|
||||||
shape.dose = shape.layer[1]
|
|
||||||
shape.layer = shape.layer[0]
|
|
||||||
return pattern
|
|
||||||
|
|
||||||
|
|
||||||
def dose2dtype(
|
|
||||||
library: Mapping[str, Pattern],
|
|
||||||
) -> Tuple[List[Pattern], List[float]]:
|
|
||||||
"""
|
|
||||||
For each shape in each pattern, set shape.layer to the tuple
|
|
||||||
(base_layer, datatype), where:
|
|
||||||
layer is chosen to be equal to the original shape.layer if it is an int,
|
|
||||||
or shape.layer[0] if it is a tuple. `str` layers raise a PatterError.
|
|
||||||
datatype is chosen arbitrarily, based on calcualted dose for each shape.
|
|
||||||
Shapes with equal calcualted dose will have the same datatype.
|
|
||||||
A list of doses is retured, providing a mapping between datatype
|
|
||||||
(list index) and dose (list entry).
|
|
||||||
|
|
||||||
Note that this function modifies the input Pattern(s).
|
|
||||||
|
|
||||||
Args:
|
|
||||||
patterns: A `Pattern` or list of patterns to write to file. Modified by this function.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
(patterns, dose_list)
|
|
||||||
patterns: modified input patterns
|
|
||||||
dose_list: A list of doses, providing a mapping between datatype (int, list index)
|
|
||||||
and dose (float, list entry).
|
|
||||||
"""
|
|
||||||
logger.warning('TODO: dose2dtype() needs to be tested!')
|
|
||||||
|
|
||||||
if not isinstance(library, Library):
|
|
||||||
library = WrapROLibrary(library)
|
|
||||||
|
|
||||||
# Get a table of (id(pat), written_dose) for each pattern and subpattern
|
|
||||||
sd_table = make_dose_table(library.find_toplevel(), library)
|
|
||||||
|
|
||||||
# Figure out all the unique doses necessary to write this pattern
|
|
||||||
# This means going through each row in sd_table and adding the dose values needed to write
|
|
||||||
# that subpattern at that dose level
|
|
||||||
dose_vals = set()
|
|
||||||
for name, pat_dose in sd_table:
|
|
||||||
pat = library[name]
|
|
||||||
for shape in pat.shapes:
|
|
||||||
dose_vals.add(shape.dose * pat_dose)
|
|
||||||
|
|
||||||
if len(dose_vals) > 256:
|
|
||||||
raise PatternError('Too many dose values: {}, maximum 256 when using dtypes.'.format(len(dose_vals)))
|
|
||||||
|
|
||||||
dose_vals_list = list(dose_vals)
|
|
||||||
|
|
||||||
# Create a new pattern for each non-1-dose entry in the dose table
|
|
||||||
# and update the shapes to reflect their new dose
|
|
||||||
new_names = {} # {(old name, dose): new name} mapping
|
|
||||||
new_lib = {} # {new_name: new_pattern} mapping
|
|
||||||
for name, pat_dose in sd_table:
|
|
||||||
mangled_name = mangle_name(name, pat_dose)
|
|
||||||
new_names[(name, pat_dose)] = mangled_name
|
|
||||||
|
|
||||||
old_pat = library[name]
|
|
||||||
|
|
||||||
if pat_dose == 1:
|
|
||||||
new_lib[mangled_name] = old_pat
|
|
||||||
continue
|
|
||||||
|
|
||||||
pat = old_pat.deepcopy()
|
|
||||||
|
|
||||||
if len(mangled_name) == 0:
|
|
||||||
raise PatternError(f'Zero-length name after mangle, originally "{name}"')
|
|
||||||
|
|
||||||
for shape in pat.shapes:
|
|
||||||
data_type = dose_vals_list.index(shape.dose * pat_dose)
|
|
||||||
if isinstance(shape.layer, int):
|
|
||||||
shape.layer = (shape.layer, data_type)
|
|
||||||
elif isinstance(shape.layer, tuple):
|
|
||||||
shape.layer = (shape.layer[0], data_type)
|
|
||||||
else:
|
|
||||||
raise PatternError(f'Invalid layer for gdsii: {shape.layer}')
|
|
||||||
|
|
||||||
new_lib[mangled_name] = pat
|
|
||||||
|
|
||||||
return new_lib, dose_vals_list
|
|
||||||
|
|
||||||
|
|
||||||
def is_gzipped(path: pathlib.Path) -> bool:
|
def is_gzipped(path: pathlib.Path) -> bool:
|
||||||
with open(path, 'rb') as stream:
|
with open(path, 'rb') as stream:
|
||||||
magic_bytes = stream.read(2)
|
magic_bytes = stream.read(2)
|
||||||
|
@ -409,7 +409,7 @@ class MutableLibrary(Library, metaclass=ABCMeta):
|
|||||||
"""
|
"""
|
||||||
Iterates through all `Pattern`s. Within each `Pattern`, it iterates
|
Iterates through all `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-, 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.
|
||||||
|
|
||||||
@ -468,7 +468,7 @@ class MutableLibrary(Library, metaclass=ABCMeta):
|
|||||||
for pat in tuple(self.values()):
|
for pat in tuple(self.values()):
|
||||||
# Store `[(index_in_shapes, values_from_normalized_form), ...]` for all shapes which
|
# Store `[(index_in_shapes, values_from_normalized_form), ...]` for all shapes which
|
||||||
# are to be replaced.
|
# are to be replaced.
|
||||||
# The `values` are `(offset, scale, rotation, dose)`.
|
# The `values` are `(offset, scale, rotation)`.
|
||||||
|
|
||||||
shape_table: MutableMapping[Tuple, List] = defaultdict(list)
|
shape_table: MutableMapping[Tuple, List] = defaultdict(list)
|
||||||
for i, shape in enumerate(pat.shapes):
|
for i, shape in enumerate(pat.shapes):
|
||||||
@ -489,9 +489,9 @@ class MutableLibrary(Library, metaclass=ABCMeta):
|
|||||||
for label in shape_table:
|
for label in shape_table:
|
||||||
target = label2name(label)
|
target = label2name(label)
|
||||||
for i, values in shape_table[label]:
|
for i, values in shape_table[label]:
|
||||||
offset, scale, rotation, mirror_x, dose = values
|
offset, scale, rotation, mirror_x = values
|
||||||
pat.addsp(target=target, offset=offset, scale=scale,
|
pat.addsp(target=target, offset=offset, scale=scale,
|
||||||
rotation=rotation, dose=dose, mirrored=(mirror_x, False))
|
rotation=rotation, mirrored=(mirror_x, False))
|
||||||
shapes_to_remove.append(i)
|
shapes_to_remove.append(i)
|
||||||
|
|
||||||
# Remove any shapes for which we have created subpatterns.
|
# Remove any shapes for which we have created subpatterns.
|
||||||
|
@ -415,20 +415,6 @@ class Pattern(AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
|||||||
self.mirror_element_centers(axis)
|
self.mirror_element_centers(axis)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def scale_element_doses(self: P, c: float) -> P:
|
|
||||||
"""
|
|
||||||
Multiply all shape and subpattern doses by a factor
|
|
||||||
|
|
||||||
Args:
|
|
||||||
c: Factor to multiply doses by
|
|
||||||
|
|
||||||
Return:
|
|
||||||
self
|
|
||||||
"""
|
|
||||||
for entry in chain(self.shapes, self.subpatterns):
|
|
||||||
entry.dose *= c
|
|
||||||
return self
|
|
||||||
|
|
||||||
def copy(self: P) -> P:
|
def copy(self: P) -> P:
|
||||||
"""
|
"""
|
||||||
Return a copy of the Pattern, deep-copying shapes and copying subpattern
|
Return a copy of the Pattern, deep-copying shapes and copying subpattern
|
||||||
|
@ -162,7 +162,6 @@ class Arc(Shape, metaclass=AutoSlots):
|
|||||||
rotation: float = 0,
|
rotation: float = 0,
|
||||||
mirrored: Sequence[bool] = (False, False),
|
mirrored: Sequence[bool] = (False, False),
|
||||||
layer: layer_t = 0,
|
layer: layer_t = 0,
|
||||||
dose: float = 1.0,
|
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
annotations: Optional[annotations_t] = None,
|
annotations: Optional[annotations_t] = None,
|
||||||
raw: bool = False,
|
raw: bool = False,
|
||||||
@ -179,7 +178,6 @@ class Arc(Shape, metaclass=AutoSlots):
|
|||||||
self._repetition = repetition
|
self._repetition = repetition
|
||||||
self._annotations = annotations if annotations is not None else {}
|
self._annotations = annotations if annotations is not None else {}
|
||||||
self._layer = layer
|
self._layer = layer
|
||||||
self._dose = dose
|
|
||||||
else:
|
else:
|
||||||
self.radii = radii
|
self.radii = radii
|
||||||
self.angles = angles
|
self.angles = angles
|
||||||
@ -189,7 +187,6 @@ class Arc(Shape, metaclass=AutoSlots):
|
|||||||
self.repetition = repetition
|
self.repetition = repetition
|
||||||
self.annotations = annotations if annotations is not None else {}
|
self.annotations = annotations if annotations is not None else {}
|
||||||
self.layer = layer
|
self.layer = layer
|
||||||
self.dose = dose
|
|
||||||
self.poly_num_points = poly_num_points
|
self.poly_num_points = poly_num_points
|
||||||
self.poly_max_arclen = poly_max_arclen
|
self.poly_max_arclen = poly_max_arclen
|
||||||
[self.mirror(a) for a, do in enumerate(mirrored) if do]
|
[self.mirror(a) for a, do in enumerate(mirrored) if do]
|
||||||
@ -256,7 +253,7 @@ class Arc(Shape, metaclass=AutoSlots):
|
|||||||
ys = numpy.hstack((ys1, ys2))
|
ys = numpy.hstack((ys1, ys2))
|
||||||
xys = numpy.vstack((xs, ys)).T
|
xys = numpy.vstack((xs, ys)).T
|
||||||
|
|
||||||
poly = Polygon(xys, dose=self.dose, layer=self.layer, offset=self.offset, rotation=self.rotation)
|
poly = Polygon(xys, layer=self.layer, offset=self.offset, rotation=self.rotation)
|
||||||
return [poly]
|
return [poly]
|
||||||
|
|
||||||
def get_bounds(self) -> NDArray[numpy.float64]:
|
def get_bounds(self) -> NDArray[numpy.float64]:
|
||||||
@ -368,7 +365,7 @@ class Arc(Shape, metaclass=AutoSlots):
|
|||||||
width = self.width
|
width = self.width
|
||||||
|
|
||||||
return ((type(self), radii, angles, width / norm_value, self.layer),
|
return ((type(self), radii, angles, width / norm_value, self.layer),
|
||||||
(self.offset, scale / norm_value, rotation, False, self.dose),
|
(self.offset, scale / norm_value, rotation, False),
|
||||||
lambda: Arc(radii=radii * norm_value, angles=angles, width=width * norm_value, layer=self.layer))
|
lambda: Arc(radii=radii * norm_value, angles=angles, width=width * norm_value, layer=self.layer))
|
||||||
|
|
||||||
def get_cap_edges(self) -> NDArray[numpy.float64]:
|
def get_cap_edges(self) -> NDArray[numpy.float64]:
|
||||||
@ -425,5 +422,4 @@ class Arc(Shape, metaclass=AutoSlots):
|
|||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
angles = f' a°{numpy.rad2deg(self.angles)}'
|
angles = f' a°{numpy.rad2deg(self.angles)}'
|
||||||
rotation = f' r°{numpy.rad2deg(self.rotation):g}' if self.rotation != 0 else ''
|
rotation = f' r°{numpy.rad2deg(self.rotation):g}' if self.rotation != 0 else ''
|
||||||
dose = f' d{self.dose:g}' if self.dose != 1 else ''
|
return f'<Arc l{self.layer} o{self.offset} r{self.radii}{angles} w{self.width:g}{rotation}>'
|
||||||
return f'<Arc l{self.layer} o{self.offset} r{self.radii}{angles} w{self.width:g}{rotation}{dose}>'
|
|
||||||
|
@ -50,7 +50,6 @@ class Circle(Shape, metaclass=AutoSlots):
|
|||||||
poly_max_arclen: Optional[float] = None,
|
poly_max_arclen: Optional[float] = None,
|
||||||
offset: ArrayLike = (0.0, 0.0),
|
offset: ArrayLike = (0.0, 0.0),
|
||||||
layer: layer_t = 0,
|
layer: layer_t = 0,
|
||||||
dose: float = 1.0,
|
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
annotations: Optional[annotations_t] = None,
|
annotations: Optional[annotations_t] = None,
|
||||||
raw: bool = False,
|
raw: bool = False,
|
||||||
@ -62,14 +61,12 @@ class Circle(Shape, metaclass=AutoSlots):
|
|||||||
self._repetition = repetition
|
self._repetition = repetition
|
||||||
self._annotations = annotations if annotations is not None else {}
|
self._annotations = annotations if annotations is not None else {}
|
||||||
self._layer = layer
|
self._layer = layer
|
||||||
self._dose = dose
|
|
||||||
else:
|
else:
|
||||||
self.radius = radius
|
self.radius = radius
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
self.repetition = repetition
|
self.repetition = repetition
|
||||||
self.annotations = annotations if annotations is not None else {}
|
self.annotations = annotations if annotations is not None else {}
|
||||||
self.layer = layer
|
self.layer = layer
|
||||||
self.dose = dose
|
|
||||||
self.poly_num_points = poly_num_points
|
self.poly_num_points = poly_num_points
|
||||||
self.poly_max_arclen = poly_max_arclen
|
self.poly_max_arclen = poly_max_arclen
|
||||||
|
|
||||||
@ -105,7 +102,7 @@ class Circle(Shape, metaclass=AutoSlots):
|
|||||||
ys = numpy.sin(thetas) * self.radius
|
ys = numpy.sin(thetas) * self.radius
|
||||||
xys = numpy.vstack((xs, ys)).T
|
xys = numpy.vstack((xs, ys)).T
|
||||||
|
|
||||||
return [Polygon(xys, offset=self.offset, dose=self.dose, layer=self.layer)]
|
return [Polygon(xys, offset=self.offset, layer=self.layer)]
|
||||||
|
|
||||||
def get_bounds(self) -> NDArray[numpy.float64]:
|
def get_bounds(self) -> NDArray[numpy.float64]:
|
||||||
return numpy.vstack((self.offset - self.radius,
|
return numpy.vstack((self.offset - self.radius,
|
||||||
@ -126,9 +123,8 @@ class Circle(Shape, metaclass=AutoSlots):
|
|||||||
rotation = 0.0
|
rotation = 0.0
|
||||||
magnitude = self.radius / norm_value
|
magnitude = self.radius / norm_value
|
||||||
return ((type(self), self.layer),
|
return ((type(self), self.layer),
|
||||||
(self.offset, magnitude, rotation, False, self.dose),
|
(self.offset, magnitude, rotation, False),
|
||||||
lambda: Circle(radius=norm_value, layer=self.layer))
|
lambda: Circle(radius=norm_value, layer=self.layer))
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
dose = f' d{self.dose:g}' if self.dose != 1 else ''
|
return f'<Circle l{self.layer} o{self.offset} r{self.radius:g}>'
|
||||||
return f'<Circle l{self.layer} o{self.offset} r{self.radius:g}{dose}>'
|
|
||||||
|
@ -97,7 +97,6 @@ class Ellipse(Shape, metaclass=AutoSlots):
|
|||||||
rotation: float = 0,
|
rotation: float = 0,
|
||||||
mirrored: Sequence[bool] = (False, False),
|
mirrored: Sequence[bool] = (False, False),
|
||||||
layer: layer_t = 0,
|
layer: layer_t = 0,
|
||||||
dose: float = 1.0,
|
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
annotations: Optional[annotations_t] = None,
|
annotations: Optional[annotations_t] = None,
|
||||||
raw: bool = False,
|
raw: bool = False,
|
||||||
@ -111,7 +110,6 @@ class Ellipse(Shape, metaclass=AutoSlots):
|
|||||||
self._repetition = repetition
|
self._repetition = repetition
|
||||||
self._annotations = annotations if annotations is not None else {}
|
self._annotations = annotations if annotations is not None else {}
|
||||||
self._layer = layer
|
self._layer = layer
|
||||||
self._dose = dose
|
|
||||||
else:
|
else:
|
||||||
self.radii = radii
|
self.radii = radii
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
@ -119,7 +117,6 @@ class Ellipse(Shape, metaclass=AutoSlots):
|
|||||||
self.repetition = repetition
|
self.repetition = repetition
|
||||||
self.annotations = annotations if annotations is not None else {}
|
self.annotations = annotations if annotations is not None else {}
|
||||||
self.layer = layer
|
self.layer = layer
|
||||||
self.dose = dose
|
|
||||||
[self.mirror(a) for a, do in enumerate(mirrored) if do]
|
[self.mirror(a) for a, do in enumerate(mirrored) if do]
|
||||||
self.poly_num_points = poly_num_points
|
self.poly_num_points = poly_num_points
|
||||||
self.poly_max_arclen = poly_max_arclen
|
self.poly_max_arclen = poly_max_arclen
|
||||||
@ -167,7 +164,7 @@ class Ellipse(Shape, metaclass=AutoSlots):
|
|||||||
ys = r1 * sin_th
|
ys = r1 * sin_th
|
||||||
xys = numpy.vstack((xs, ys)).T
|
xys = numpy.vstack((xs, ys)).T
|
||||||
|
|
||||||
poly = Polygon(xys, dose=self.dose, layer=self.layer, offset=self.offset, rotation=self.rotation)
|
poly = Polygon(xys, layer=self.layer, offset=self.offset, rotation=self.rotation)
|
||||||
return [poly]
|
return [poly]
|
||||||
|
|
||||||
def get_bounds(self) -> NDArray[numpy.float64]:
|
def get_bounds(self) -> NDArray[numpy.float64]:
|
||||||
@ -199,10 +196,9 @@ class Ellipse(Shape, metaclass=AutoSlots):
|
|||||||
scale = self.radius_y
|
scale = self.radius_y
|
||||||
angle = (self.rotation + pi / 2) % pi
|
angle = (self.rotation + pi / 2) % pi
|
||||||
return ((type(self), radii, self.layer),
|
return ((type(self), radii, self.layer),
|
||||||
(self.offset, scale / norm_value, angle, False, self.dose),
|
(self.offset, scale / norm_value, angle, False),
|
||||||
lambda: Ellipse(radii=radii * norm_value, layer=self.layer))
|
lambda: Ellipse(radii=radii * norm_value, layer=self.layer))
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
rotation = f' r{self.rotation*180/pi:g}' if self.rotation != 0 else ''
|
rotation = f' r{self.rotation*180/pi:g}' if self.rotation != 0 else ''
|
||||||
dose = f' d{self.dose:g}' if self.dose != 1 else ''
|
return f'<Ellipse l{self.layer} o{self.offset} r{self.radii}{rotation}>'
|
||||||
return f'<Ellipse l{self.layer} o{self.offset} r{self.radii}{rotation}{dose}>'
|
|
||||||
|
@ -151,7 +151,6 @@ class Path(Shape, metaclass=AutoSlots):
|
|||||||
rotation: float = 0,
|
rotation: float = 0,
|
||||||
mirrored: Sequence[bool] = (False, False),
|
mirrored: Sequence[bool] = (False, False),
|
||||||
layer: layer_t = 0,
|
layer: layer_t = 0,
|
||||||
dose: float = 1.0,
|
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
annotations: Optional[annotations_t] = None,
|
annotations: Optional[annotations_t] = None,
|
||||||
raw: bool = False,
|
raw: bool = False,
|
||||||
@ -167,7 +166,6 @@ class Path(Shape, metaclass=AutoSlots):
|
|||||||
self._repetition = repetition
|
self._repetition = repetition
|
||||||
self._annotations = annotations if annotations is not None else {}
|
self._annotations = annotations if annotations is not None else {}
|
||||||
self._layer = layer
|
self._layer = layer
|
||||||
self._dose = dose
|
|
||||||
self._width = width
|
self._width = width
|
||||||
self._cap = cap
|
self._cap = cap
|
||||||
self._cap_extensions = cap_extensions
|
self._cap_extensions = cap_extensions
|
||||||
@ -177,7 +175,6 @@ class Path(Shape, metaclass=AutoSlots):
|
|||||||
self.repetition = repetition
|
self.repetition = repetition
|
||||||
self.annotations = annotations if annotations is not None else {}
|
self.annotations = annotations if annotations is not None else {}
|
||||||
self.layer = layer
|
self.layer = layer
|
||||||
self.dose = dose
|
|
||||||
self.width = width
|
self.width = width
|
||||||
self.cap = cap
|
self.cap = cap
|
||||||
self.cap_extensions = cap_extensions
|
self.cap_extensions = cap_extensions
|
||||||
@ -204,7 +201,6 @@ class Path(Shape, metaclass=AutoSlots):
|
|||||||
rotation: float = 0,
|
rotation: float = 0,
|
||||||
mirrored: Sequence[bool] = (False, False),
|
mirrored: Sequence[bool] = (False, False),
|
||||||
layer: layer_t = 0,
|
layer: layer_t = 0,
|
||||||
dose: float = 1.0,
|
|
||||||
) -> 'Path':
|
) -> 'Path':
|
||||||
"""
|
"""
|
||||||
Build a path by specifying the turn angles and travel distances
|
Build a path by specifying the turn angles and travel distances
|
||||||
@ -225,7 +221,6 @@ class Path(Shape, metaclass=AutoSlots):
|
|||||||
`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)`
|
||||||
layer: Layer, default `0`
|
layer: Layer, default `0`
|
||||||
dose: Dose, default `1.0`
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The resulting Path object
|
The resulting Path object
|
||||||
@ -240,7 +235,7 @@ class Path(Shape, metaclass=AutoSlots):
|
|||||||
|
|
||||||
return Path(vertices=verts, width=width, cap=cap, cap_extensions=cap_extensions,
|
return Path(vertices=verts, width=width, cap=cap, cap_extensions=cap_extensions,
|
||||||
offset=offset, rotation=rotation, mirrored=mirrored,
|
offset=offset, rotation=rotation, mirrored=mirrored,
|
||||||
layer=layer, dose=dose)
|
layer=layer)
|
||||||
|
|
||||||
def to_polygons(
|
def to_polygons(
|
||||||
self,
|
self,
|
||||||
@ -255,7 +250,7 @@ class Path(Shape, metaclass=AutoSlots):
|
|||||||
|
|
||||||
if self.width == 0:
|
if self.width == 0:
|
||||||
verts = numpy.vstack((v, v[::-1]))
|
verts = numpy.vstack((v, v[::-1]))
|
||||||
return [Polygon(offset=self.offset, vertices=verts, dose=self.dose, layer=self.layer)]
|
return [Polygon(offset=self.offset, vertices=verts, layer=self.layer)]
|
||||||
|
|
||||||
perp = dvdir[:, ::-1] * [[1, -1]] * self.width / 2
|
perp = dvdir[:, ::-1] * [[1, -1]] * self.width / 2
|
||||||
|
|
||||||
@ -306,12 +301,12 @@ class Path(Shape, metaclass=AutoSlots):
|
|||||||
o1.append(v[-1] - perp[-1])
|
o1.append(v[-1] - perp[-1])
|
||||||
verts = numpy.vstack((o0, o1[::-1]))
|
verts = numpy.vstack((o0, o1[::-1]))
|
||||||
|
|
||||||
polys = [Polygon(offset=self.offset, vertices=verts, dose=self.dose, layer=self.layer)]
|
polys = [Polygon(offset=self.offset, vertices=verts, layer=self.layer)]
|
||||||
|
|
||||||
if self.cap == PathCap.Circle:
|
if self.cap == PathCap.Circle:
|
||||||
#for vert in v: # not sure if every vertex, or just ends?
|
#for vert in v: # not sure if every vertex, or just ends?
|
||||||
for vert in [v[0], v[-1]]:
|
for vert in [v[0], v[-1]]:
|
||||||
circ = Circle(offset=vert, radius=self.width / 2, dose=self.dose, layer=self.layer)
|
circ = Circle(offset=vert, radius=self.width / 2, layer=self.layer)
|
||||||
polys += circ.to_polygons(poly_num_points=poly_num_points, poly_max_arclen=poly_max_arclen)
|
polys += circ.to_polygons(poly_num_points=poly_num_points, poly_max_arclen=poly_max_arclen)
|
||||||
|
|
||||||
return polys
|
return polys
|
||||||
@ -372,7 +367,7 @@ class Path(Shape, metaclass=AutoSlots):
|
|||||||
width0 = self.width / norm_value
|
width0 = self.width / norm_value
|
||||||
|
|
||||||
return ((type(self), reordered_vertices.data.tobytes(), width0, self.cap, self.layer),
|
return ((type(self), reordered_vertices.data.tobytes(), width0, self.cap, self.layer),
|
||||||
(offset, scale / norm_value, rotation, False, self.dose),
|
(offset, scale / norm_value, rotation, False),
|
||||||
lambda: Path(reordered_vertices * norm_value, width=self.width * norm_value,
|
lambda: Path(reordered_vertices * norm_value, width=self.width * norm_value,
|
||||||
cap=self.cap, layer=self.layer))
|
cap=self.cap, layer=self.layer))
|
||||||
|
|
||||||
@ -419,5 +414,4 @@ class Path(Shape, metaclass=AutoSlots):
|
|||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
centroid = self.offset + self.vertices.mean(axis=0)
|
centroid = self.offset + self.vertices.mean(axis=0)
|
||||||
dose = f' d{self.dose:g}' if self.dose != 1 else ''
|
return f'<Path l{self.layer} centroid {centroid} v{len(self.vertices)} w{self.width} c{self.cap}>'
|
||||||
return f'<Path l{self.layer} centroid {centroid} v{len(self.vertices)} w{self.width} c{self.cap}{dose}>'
|
|
||||||
|
@ -79,7 +79,6 @@ class Polygon(Shape, metaclass=AutoSlots):
|
|||||||
rotation: float = 0.0,
|
rotation: float = 0.0,
|
||||||
mirrored: Sequence[bool] = (False, False),
|
mirrored: Sequence[bool] = (False, False),
|
||||||
layer: layer_t = 0,
|
layer: layer_t = 0,
|
||||||
dose: float = 1.0,
|
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
annotations: Optional[annotations_t] = None,
|
annotations: Optional[annotations_t] = None,
|
||||||
raw: bool = False,
|
raw: bool = False,
|
||||||
@ -92,14 +91,12 @@ class Polygon(Shape, metaclass=AutoSlots):
|
|||||||
self._repetition = repetition
|
self._repetition = repetition
|
||||||
self._annotations = annotations if annotations is not None else {}
|
self._annotations = annotations if annotations is not None else {}
|
||||||
self._layer = layer
|
self._layer = layer
|
||||||
self._dose = dose
|
|
||||||
else:
|
else:
|
||||||
self.vertices = vertices
|
self.vertices = vertices
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
self.repetition = repetition
|
self.repetition = repetition
|
||||||
self.annotations = annotations if annotations is not None else {}
|
self.annotations = annotations if annotations is not None else {}
|
||||||
self.layer = layer
|
self.layer = layer
|
||||||
self.dose = dose
|
|
||||||
self.rotate(rotation)
|
self.rotate(rotation)
|
||||||
[self.mirror(a) for a, do in enumerate(mirrored) if do]
|
[self.mirror(a) for a, do in enumerate(mirrored) if do]
|
||||||
|
|
||||||
@ -118,7 +115,6 @@ class Polygon(Shape, metaclass=AutoSlots):
|
|||||||
rotation: float = 0.0,
|
rotation: float = 0.0,
|
||||||
offset: ArrayLike = (0.0, 0.0),
|
offset: ArrayLike = (0.0, 0.0),
|
||||||
layer: layer_t = 0,
|
layer: layer_t = 0,
|
||||||
dose: float = 1.0,
|
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
) -> 'Polygon':
|
) -> 'Polygon':
|
||||||
"""
|
"""
|
||||||
@ -129,7 +125,6 @@ class Polygon(Shape, metaclass=AutoSlots):
|
|||||||
rotation: Rotation counterclockwise, in radians
|
rotation: Rotation counterclockwise, in radians
|
||||||
offset: Offset, default `(0, 0)`
|
offset: Offset, default `(0, 0)`
|
||||||
layer: Layer, default `0`
|
layer: Layer, default `0`
|
||||||
dose: Dose, default `1.0`
|
|
||||||
repetition: `Repetition` object, default `None`
|
repetition: `Repetition` object, default `None`
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -140,8 +135,7 @@ class Polygon(Shape, metaclass=AutoSlots):
|
|||||||
[+1, +1],
|
[+1, +1],
|
||||||
[+1, -1]], dtype=float)
|
[+1, -1]], dtype=float)
|
||||||
vertices = 0.5 * side_length * norm_square
|
vertices = 0.5 * side_length * norm_square
|
||||||
poly = Polygon(vertices, offset=offset, layer=layer, dose=dose,
|
poly = Polygon(vertices, offset=offset, layer=layer, repetition=repetition)
|
||||||
repetition=repetition)
|
|
||||||
poly.rotate(rotation)
|
poly.rotate(rotation)
|
||||||
return poly
|
return poly
|
||||||
|
|
||||||
@ -153,7 +147,6 @@ class Polygon(Shape, metaclass=AutoSlots):
|
|||||||
rotation: float = 0,
|
rotation: float = 0,
|
||||||
offset: ArrayLike = (0.0, 0.0),
|
offset: ArrayLike = (0.0, 0.0),
|
||||||
layer: layer_t = 0,
|
layer: layer_t = 0,
|
||||||
dose: float = 1.0,
|
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
) -> 'Polygon':
|
) -> 'Polygon':
|
||||||
"""
|
"""
|
||||||
@ -165,7 +158,6 @@ class Polygon(Shape, metaclass=AutoSlots):
|
|||||||
rotation: Rotation counterclockwise, in radians
|
rotation: Rotation counterclockwise, in radians
|
||||||
offset: Offset, default `(0, 0)`
|
offset: Offset, default `(0, 0)`
|
||||||
layer: Layer, default `0`
|
layer: Layer, default `0`
|
||||||
dose: Dose, default `1.0`
|
|
||||||
repetition: `Repetition` object, default `None`
|
repetition: `Repetition` object, default `None`
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -175,8 +167,7 @@ class Polygon(Shape, metaclass=AutoSlots):
|
|||||||
[-lx, +ly],
|
[-lx, +ly],
|
||||||
[+lx, +ly],
|
[+lx, +ly],
|
||||||
[+lx, -ly]], dtype=float)
|
[+lx, -ly]], dtype=float)
|
||||||
poly = Polygon(vertices, offset=offset, layer=layer, dose=dose,
|
poly = Polygon(vertices, offset=offset, layer=layer, repetition=repetition)
|
||||||
repetition=repetition)
|
|
||||||
poly.rotate(rotation)
|
poly.rotate(rotation)
|
||||||
return poly
|
return poly
|
||||||
|
|
||||||
@ -192,7 +183,6 @@ class Polygon(Shape, metaclass=AutoSlots):
|
|||||||
ymax: Optional[float] = None,
|
ymax: Optional[float] = None,
|
||||||
ly: Optional[float] = None,
|
ly: Optional[float] = None,
|
||||||
layer: layer_t = 0,
|
layer: layer_t = 0,
|
||||||
dose: float = 1.0,
|
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
) -> 'Polygon':
|
) -> 'Polygon':
|
||||||
"""
|
"""
|
||||||
@ -211,7 +201,6 @@ class Polygon(Shape, metaclass=AutoSlots):
|
|||||||
ymax: Maximum y coordinate
|
ymax: Maximum y coordinate
|
||||||
ly: Length along y direction
|
ly: Length along y direction
|
||||||
layer: Layer, default `0`
|
layer: Layer, default `0`
|
||||||
dose: Dose, default `1.0`
|
|
||||||
repetition: `Repetition` object, default `None`
|
repetition: `Repetition` object, default `None`
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -277,8 +266,7 @@ class Polygon(Shape, metaclass=AutoSlots):
|
|||||||
else:
|
else:
|
||||||
raise PatternError('Two of ymin, yctr, ymax, ly must be None!')
|
raise PatternError('Two of ymin, yctr, ymax, ly must be None!')
|
||||||
|
|
||||||
poly = Polygon.rectangle(lx, ly, offset=(xctr, yctr),
|
poly = Polygon.rectangle(lx, ly, offset=(xctr, yctr), layer=layer, repetition=repetition)
|
||||||
layer=layer, dose=dose, repetition=repetition)
|
|
||||||
return poly
|
return poly
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -290,7 +278,6 @@ class Polygon(Shape, metaclass=AutoSlots):
|
|||||||
center: ArrayLike = (0.0, 0.0),
|
center: ArrayLike = (0.0, 0.0),
|
||||||
rotation: float = 0.0,
|
rotation: float = 0.0,
|
||||||
layer: layer_t = 0,
|
layer: layer_t = 0,
|
||||||
dose: float = 1.0,
|
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
) -> 'Polygon':
|
) -> 'Polygon':
|
||||||
"""
|
"""
|
||||||
@ -310,7 +297,6 @@ class Polygon(Shape, metaclass=AutoSlots):
|
|||||||
`0` results in four axis-aligned sides (the long sides of the
|
`0` results in four axis-aligned sides (the long sides of the
|
||||||
irregular octagon).
|
irregular octagon).
|
||||||
layer: Layer, default `0`
|
layer: Layer, default `0`
|
||||||
dose: Dose, default `1.0`
|
|
||||||
repetition: `Repetition` object, default `None`
|
repetition: `Repetition` object, default `None`
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -337,7 +323,7 @@ class Polygon(Shape, metaclass=AutoSlots):
|
|||||||
side_length = 2 * inner_radius / s
|
side_length = 2 * inner_radius / s
|
||||||
|
|
||||||
vertices = 0.5 * side_length * norm_oct
|
vertices = 0.5 * side_length * norm_oct
|
||||||
poly = Polygon(vertices, offset=center, layer=layer, dose=dose, repetition=repetition)
|
poly = Polygon(vertices, offset=center, layer=layer, repetition=repetition)
|
||||||
poly.rotate(rotation)
|
poly.rotate(rotation)
|
||||||
return poly
|
return poly
|
||||||
|
|
||||||
@ -390,7 +376,7 @@ class Polygon(Shape, metaclass=AutoSlots):
|
|||||||
# TODO: normalize mirroring?
|
# TODO: normalize mirroring?
|
||||||
|
|
||||||
return ((type(self), reordered_vertices.data.tobytes(), self.layer),
|
return ((type(self), reordered_vertices.data.tobytes(), self.layer),
|
||||||
(offset, scale / norm_value, rotation, False, self.dose),
|
(offset, scale / norm_value, rotation, False),
|
||||||
lambda: Polygon(reordered_vertices * norm_value, layer=self.layer))
|
lambda: Polygon(reordered_vertices * norm_value, layer=self.layer))
|
||||||
|
|
||||||
def clean_vertices(self) -> 'Polygon':
|
def clean_vertices(self) -> 'Polygon':
|
||||||
@ -425,5 +411,4 @@ class Polygon(Shape, metaclass=AutoSlots):
|
|||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
centroid = self.offset + self.vertices.mean(axis=0)
|
centroid = self.offset + self.vertices.mean(axis=0)
|
||||||
dose = f' d{self.dose:g}' if self.dose != 1 else ''
|
return f'<Polygon l{self.layer} centroid {centroid} v{len(self.vertices)}>'
|
||||||
return f'<Polygon l{self.layer} centroid {centroid} v{len(self.vertices)}{dose}>'
|
|
||||||
|
@ -5,8 +5,8 @@ import numpy
|
|||||||
from numpy.typing import NDArray, ArrayLike
|
from numpy.typing import NDArray, ArrayLike
|
||||||
|
|
||||||
from ..traits import (
|
from ..traits import (
|
||||||
PositionableImpl, LayerableImpl, DoseableImpl,
|
|
||||||
Rotatable, Mirrorable, Copyable, Scalable,
|
Rotatable, Mirrorable, Copyable, Scalable,
|
||||||
|
PositionableImpl, LayerableImpl,
|
||||||
PivotableImpl, RepeatableImpl, AnnotatableImpl,
|
PivotableImpl, RepeatableImpl, AnnotatableImpl,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ DEFAULT_POLY_NUM_POINTS = 24
|
|||||||
T = TypeVar('T', bound='Shape')
|
T = TypeVar('T', bound='Shape')
|
||||||
|
|
||||||
|
|
||||||
class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable, Copyable, Scalable,
|
class Shape(PositionableImpl, LayerableImpl, Rotatable, Mirrorable, Copyable, Scalable,
|
||||||
PivotableImpl, RepeatableImpl, AnnotatableImpl, metaclass=ABCMeta):
|
PivotableImpl, RepeatableImpl, AnnotatableImpl, metaclass=ABCMeta):
|
||||||
"""
|
"""
|
||||||
Abstract class specifying functions common to all shapes.
|
Abstract class specifying functions common to all shapes.
|
||||||
@ -68,7 +68,7 @@ class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable
|
|||||||
@abstractmethod
|
@abstractmethod
|
||||||
def normalized_form(self: T, norm_value: int) -> normalized_shape_tuple:
|
def normalized_form(self: T, norm_value: int) -> normalized_shape_tuple:
|
||||||
"""
|
"""
|
||||||
Writes the shape in a standardized notation, with offset, scale, rotation, and dose
|
Writes the shape in a standardized notation, with offset, scale, and rotation
|
||||||
information separated out from the remaining values.
|
information separated out from the remaining values.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -83,7 +83,7 @@ class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable
|
|||||||
`(intrinsic, extrinsic, constructor)`. These are further broken down as:
|
`(intrinsic, extrinsic, constructor)`. These are further broken down as:
|
||||||
`intrinsic`: A tuple of basic types containing all information about the instance that
|
`intrinsic`: A tuple of basic types containing all information about the instance that
|
||||||
is not contained in 'extrinsic'. Usually, `intrinsic[0] == type(self)`.
|
is not contained in 'extrinsic'. Usually, `intrinsic[0] == type(self)`.
|
||||||
`extrinsic`: `([x_offset, y_offset], scale, rotation, mirror_across_x_axis, dose)`
|
`extrinsic`: `([x_offset, y_offset], scale, rotation, mirror_across_x_axis)`
|
||||||
`constructor`: A callable (no arguments) which returns an instance of `type(self)` with
|
`constructor`: A callable (no arguments) which returns an instance of `type(self)` with
|
||||||
internal state equivalent to `intrinsic`.
|
internal state equivalent to `intrinsic`.
|
||||||
"""
|
"""
|
||||||
@ -195,12 +195,10 @@ class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable
|
|||||||
vertex_lists.append(vlist)
|
vertex_lists.append(vlist)
|
||||||
polygon_contours.append(numpy.vstack(vertex_lists))
|
polygon_contours.append(numpy.vstack(vertex_lists))
|
||||||
|
|
||||||
manhattan_polygons = []
|
manhattan_polygons = [
|
||||||
for contour in polygon_contours:
|
Polygon(vertices=contour, layer=self.layer)
|
||||||
manhattan_polygons.append(Polygon(
|
for contour in polygon_contours
|
||||||
vertices=contour,
|
]
|
||||||
layer=self.layer,
|
|
||||||
dose=self.dose))
|
|
||||||
|
|
||||||
return manhattan_polygons
|
return manhattan_polygons
|
||||||
|
|
||||||
@ -298,6 +296,6 @@ class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable
|
|||||||
manhattan_polygons.append(Polygon(
|
manhattan_polygons.append(Polygon(
|
||||||
vertices=vertices,
|
vertices=vertices,
|
||||||
layer=self.layer,
|
layer=self.layer,
|
||||||
dose=self.dose))
|
))
|
||||||
|
|
||||||
return manhattan_polygons
|
return manhattan_polygons
|
||||||
|
@ -70,7 +70,6 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
|
|||||||
rotation: float = 0.0,
|
rotation: float = 0.0,
|
||||||
mirrored: ArrayLike = (False, False),
|
mirrored: ArrayLike = (False, False),
|
||||||
layer: layer_t = 0,
|
layer: layer_t = 0,
|
||||||
dose: float = 1.0,
|
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
annotations: Optional[annotations_t] = None,
|
annotations: Optional[annotations_t] = None,
|
||||||
raw: bool = False,
|
raw: bool = False,
|
||||||
@ -80,7 +79,6 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
|
|||||||
assert(isinstance(mirrored, numpy.ndarray))
|
assert(isinstance(mirrored, numpy.ndarray))
|
||||||
self._offset = offset
|
self._offset = offset
|
||||||
self._layer = layer
|
self._layer = layer
|
||||||
self._dose = dose
|
|
||||||
self._string = string
|
self._string = string
|
||||||
self._height = height
|
self._height = height
|
||||||
self._rotation = rotation
|
self._rotation = rotation
|
||||||
@ -90,7 +88,6 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
|
|||||||
else:
|
else:
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
self.layer = layer
|
self.layer = layer
|
||||||
self.dose = dose
|
|
||||||
self.string = string
|
self.string = string
|
||||||
self.height = height
|
self.height = height
|
||||||
self.rotation = rotation
|
self.rotation = rotation
|
||||||
@ -119,7 +116,7 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
|
|||||||
|
|
||||||
# Move these polygons to the right of the previous letter
|
# Move these polygons to the right of the previous letter
|
||||||
for xys in raw_polys:
|
for xys in raw_polys:
|
||||||
poly = Polygon(xys, dose=self.dose, layer=self.layer)
|
poly = Polygon(xys, layer=self.layer)
|
||||||
poly.mirror2d(self.mirrored)
|
poly.mirror2d(self.mirrored)
|
||||||
poly.scale_by(self.height)
|
poly.scale_by(self.height)
|
||||||
poly.offset = self.offset + [total_advance, 0]
|
poly.offset = self.offset + [total_advance, 0]
|
||||||
@ -144,7 +141,7 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
|
|||||||
rotation += self.rotation
|
rotation += self.rotation
|
||||||
rotation %= 2 * pi
|
rotation %= 2 * pi
|
||||||
return ((type(self), self.string, self.font_path, self.layer),
|
return ((type(self), self.string, self.font_path, self.layer),
|
||||||
(self.offset, self.height / norm_value, rotation, mirror_x, self.dose),
|
(self.offset, self.height / norm_value, rotation, mirror_x),
|
||||||
lambda: Text(string=self.string,
|
lambda: Text(string=self.string,
|
||||||
height=self.height * norm_value,
|
height=self.height * norm_value,
|
||||||
font_path=self.font_path,
|
font_path=self.font_path,
|
||||||
@ -254,6 +251,5 @@ def get_char_as_polygons(
|
|||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
rotation = f' r°{self.rotation*180/pi:g}' if self.rotation != 0 else ''
|
rotation = f' r°{self.rotation*180/pi:g}' if self.rotation != 0 else ''
|
||||||
dose = f' d{self.dose:g}' if self.dose != 1 else ''
|
|
||||||
mirrored = ' m{:d}{:d}'.format(*self.mirrored) if self.mirrored.any() else ''
|
mirrored = ' m{:d}{:d}'.format(*self.mirrored) if self.mirrored.any() else ''
|
||||||
return f'<TextShape "{self.string}" l{self.layer} o{self.offset} h{self.height:g}{rotation}{mirrored}{dose}>'
|
return f'<TextShape "{self.string}" l{self.layer} o{self.offset} h{self.height:g}{rotation}{mirrored}>'
|
||||||
|
@ -15,7 +15,7 @@ from .error import PatternError
|
|||||||
from .utils import is_scalar, AutoSlots, annotations_t
|
from .utils import is_scalar, AutoSlots, annotations_t
|
||||||
from .repetition import Repetition
|
from .repetition import Repetition
|
||||||
from .traits import (
|
from .traits import (
|
||||||
PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl,
|
PositionableImpl, RotatableImpl, ScalableImpl,
|
||||||
Mirrorable, PivotableImpl, Copyable, RepeatableImpl, AnnotatableImpl,
|
Mirrorable, PivotableImpl, Copyable, RepeatableImpl, AnnotatableImpl,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ if TYPE_CHECKING:
|
|||||||
S = TypeVar('S', bound='SubPattern')
|
S = TypeVar('S', bound='SubPattern')
|
||||||
|
|
||||||
|
|
||||||
class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mirrorable,
|
class SubPattern(PositionableImpl, RotatableImpl, ScalableImpl, Mirrorable,
|
||||||
PivotableImpl, Copyable, RepeatableImpl, AnnotatableImpl,
|
PivotableImpl, Copyable, RepeatableImpl, AnnotatableImpl,
|
||||||
metaclass=AutoSlots):
|
metaclass=AutoSlots):
|
||||||
"""
|
"""
|
||||||
@ -49,7 +49,6 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
|||||||
offset: ArrayLike = (0.0, 0.0),
|
offset: ArrayLike = (0.0, 0.0),
|
||||||
rotation: float = 0.0,
|
rotation: float = 0.0,
|
||||||
mirrored: Optional[Sequence[bool]] = None,
|
mirrored: Optional[Sequence[bool]] = None,
|
||||||
dose: float = 1.0,
|
|
||||||
scale: float = 1.0,
|
scale: float = 1.0,
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
annotations: Optional[annotations_t] = None,
|
annotations: Optional[annotations_t] = None,
|
||||||
@ -60,14 +59,12 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
|||||||
offset: (x, y) offset applied to the referenced pattern. Not affected by rotation etc.
|
offset: (x, y) offset applied to the referenced pattern. Not affected by rotation etc.
|
||||||
rotation: Rotation (radians, counterclockwise) relative to the referenced pattern's (0, 0).
|
rotation: Rotation (radians, counterclockwise) relative to the referenced pattern's (0, 0).
|
||||||
mirrored: Whether to mirror the referenced pattern across its x and y axes.
|
mirrored: Whether to mirror the referenced pattern across its x and y axes.
|
||||||
dose: Scaling factor applied to the dose.
|
|
||||||
scale: Scaling factor applied to the pattern's geometry.
|
scale: Scaling factor applied to the pattern's geometry.
|
||||||
repetition: `Repetition` object, default `None`
|
repetition: `Repetition` object, default `None`
|
||||||
"""
|
"""
|
||||||
self.target = target
|
self.target = target
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
self.rotation = rotation
|
self.rotation = rotation
|
||||||
self.dose = dose
|
|
||||||
self.scale = scale
|
self.scale = scale
|
||||||
if mirrored is None:
|
if mirrored is None:
|
||||||
mirrored = (False, False)
|
mirrored = (False, False)
|
||||||
@ -80,7 +77,6 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
|||||||
target=self.target,
|
target=self.target,
|
||||||
offset=self.offset.copy(),
|
offset=self.offset.copy(),
|
||||||
rotation=self.rotation,
|
rotation=self.rotation,
|
||||||
dose=self.dose,
|
|
||||||
scale=self.scale,
|
scale=self.scale,
|
||||||
mirrored=self.mirrored.copy(),
|
mirrored=self.mirrored.copy(),
|
||||||
repetition=copy.deepcopy(self.repetition),
|
repetition=copy.deepcopy(self.repetition),
|
||||||
@ -150,8 +146,6 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
|||||||
pattern.rotate_around((0.0, 0.0), self.rotation)
|
pattern.rotate_around((0.0, 0.0), self.rotation)
|
||||||
if numpy.any(self.offset):
|
if numpy.any(self.offset):
|
||||||
pattern.translate_elements(self.offset)
|
pattern.translate_elements(self.offset)
|
||||||
if self.dose != 1:
|
|
||||||
pattern.scale_element_doses(self.dose)
|
|
||||||
|
|
||||||
if self.repetition is not None:
|
if self.repetition is not None:
|
||||||
combined = type(pattern)()
|
combined = type(pattern)()
|
||||||
@ -204,5 +198,4 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
|||||||
rotation = f' r{self.rotation*180/pi:g}' if self.rotation != 0 else ''
|
rotation = f' r{self.rotation*180/pi:g}' if self.rotation != 0 else ''
|
||||||
scale = f' d{self.scale:g}' if self.scale != 1 else ''
|
scale = f' d{self.scale:g}' if self.scale != 1 else ''
|
||||||
mirrored = ' m{:d}{:d}'.format(*self.mirrored) if self.mirrored.any() else ''
|
mirrored = ' m{:d}{:d}'.format(*self.mirrored) if self.mirrored.any() else ''
|
||||||
dose = f' d{self.dose:g}' if self.dose != 1 else ''
|
return f'<SubPattern {name} at {self.offset}{rotation}{scale}{mirrored}>'
|
||||||
return f'<SubPattern {name} at {self.offset}{rotation}{scale}{mirrored}{dose}>'
|
|
||||||
|
@ -3,7 +3,6 @@ Traits (mixins) and default implementations
|
|||||||
"""
|
"""
|
||||||
from .positionable import Positionable, PositionableImpl
|
from .positionable import Positionable, PositionableImpl
|
||||||
from .layerable import Layerable, LayerableImpl
|
from .layerable import Layerable, LayerableImpl
|
||||||
from .doseable import Doseable, DoseableImpl
|
|
||||||
from .rotatable import Rotatable, RotatableImpl, Pivotable, PivotableImpl
|
from .rotatable import Rotatable, RotatableImpl, Pivotable, PivotableImpl
|
||||||
from .repeatable import Repeatable, RepeatableImpl
|
from .repeatable import Repeatable, RepeatableImpl
|
||||||
from .scalable import Scalable, ScalableImpl
|
from .scalable import Scalable, ScalableImpl
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
from typing import TypeVar
|
|
||||||
from abc import ABCMeta, abstractmethod
|
|
||||||
|
|
||||||
from ..error import MasqueError
|
|
||||||
|
|
||||||
|
|
||||||
T = TypeVar('T', bound='Doseable')
|
|
||||||
I = TypeVar('I', bound='DoseableImpl')
|
|
||||||
|
|
||||||
|
|
||||||
class Doseable(metaclass=ABCMeta):
|
|
||||||
"""
|
|
||||||
Abstract class for all doseable entities
|
|
||||||
"""
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
'''
|
|
||||||
---- Properties
|
|
||||||
'''
|
|
||||||
@property
|
|
||||||
@abstractmethod
|
|
||||||
def dose(self) -> float:
|
|
||||||
"""
|
|
||||||
Dose (float >= 0)
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
# @dose.setter
|
|
||||||
# @abstractmethod
|
|
||||||
# def dose(self, val: float):
|
|
||||||
# pass
|
|
||||||
|
|
||||||
'''
|
|
||||||
---- Methods
|
|
||||||
'''
|
|
||||||
@abstractmethod
|
|
||||||
def set_dose(self: T, dose: float) -> T:
|
|
||||||
"""
|
|
||||||
Set the dose
|
|
||||||
|
|
||||||
Args:
|
|
||||||
dose: new value for dose
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
self
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class DoseableImpl(Doseable, metaclass=ABCMeta):
|
|
||||||
"""
|
|
||||||
Simple implementation of Doseable
|
|
||||||
"""
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
_dose: float
|
|
||||||
""" Dose """
|
|
||||||
|
|
||||||
'''
|
|
||||||
---- Non-abstract properties
|
|
||||||
'''
|
|
||||||
@property
|
|
||||||
def dose(self) -> float:
|
|
||||||
return self._dose
|
|
||||||
|
|
||||||
@dose.setter
|
|
||||||
def dose(self, val: float) -> None:
|
|
||||||
if not val >= 0:
|
|
||||||
raise MasqueError('Dose must be non-negative')
|
|
||||||
self._dose = val
|
|
||||||
|
|
||||||
'''
|
|
||||||
---- Non-abstract methods
|
|
||||||
'''
|
|
||||||
def set_dose(self: I, dose: float) -> I:
|
|
||||||
self.dose = dose
|
|
||||||
return self
|
|
Loading…
Reference in New Issue
Block a user