reformat some multiline arg lists and add missing 'None' return types

nolock
jan 2 years ago
parent 250107e41b
commit 89f327ba37

@ -167,12 +167,13 @@ class Device(Copyable, Mirrorable):
_dead: bool _dead: bool
""" If True, plug()/place() are skipped (for debugging)""" """ If True, plug()/place() are skipped (for debugging)"""
def __init__(self, def __init__(
pattern: Optional[Pattern] = None, self,
ports: Optional[Dict[str, Port]] = None, pattern: Optional[Pattern] = None,
*, ports: Optional[Dict[str, Port]] = None,
name: Optional[str] = None, *,
) -> None: name: Optional[str] = None,
) -> None:
""" """
If `ports` is `None`, two default ports ('A' and 'B') are created. If `ports` is `None`, two default ports ('A' and 'B') are created.
Both are placed at (0, 0) and have default `ptype`, but 'A' has rotation 0 Both are placed at (0, 0) and have default `ptype`, but 'A' has rotation 0
@ -218,10 +219,11 @@ class Device(Copyable, Mirrorable):
else: else:
return {k: self.ports[k] for k in key} return {k: self.ports[k] for k in key}
def rename_ports(self: D, def rename_ports(
mapping: Dict[str, Optional[str]], self: D,
overwrite: bool = False, mapping: Dict[str, Optional[str]],
) -> D: overwrite: bool = False,
) -> D:
""" """
Renames ports as specified by `mapping`. Renames ports as specified by `mapping`.
Ports can be explicitly deleted by mapping them to `None`. Ports can be explicitly deleted by mapping them to `None`.
@ -248,11 +250,12 @@ class Device(Copyable, Mirrorable):
self.ports.update(renamed) # type: ignore self.ports.update(renamed) # type: ignore
return self return self
def check_ports(self: D, def check_ports(
other_names: Iterable[str], self: D,
map_in: Optional[Dict[str, str]] = None, other_names: Iterable[str],
map_out: Optional[Dict[str, Optional[str]]] = None, map_in: Optional[Dict[str, str]] = None,
) -> D: map_out: Optional[Dict[str, Optional[str]]] = None,
) -> D:
""" """
Given the provided port mappings, check that: Given the provided port mappings, check that:
- All of the ports specified in the mappings exist - All of the ports specified in the mappings exist
@ -332,12 +335,13 @@ class Device(Copyable, Mirrorable):
new = Device(pat, ports=self.ports) new = Device(pat, ports=self.ports)
return new return new
def as_interface(self, def as_interface(
name: str, self,
in_prefix: str = 'in_', name: str,
out_prefix: str = '', in_prefix: str = 'in_',
port_map: Optional[Union[Dict[str, str], Sequence[str]]] = None out_prefix: str = '',
) -> 'Device': port_map: Optional[Union[Dict[str, str], Sequence[str]]] = None
) -> 'Device':
""" """
Begin building a new device based on all or some of the ports in the Begin building a new device based on all or some of the ports in the
current device. Do not include the current device; instead use it current device. Do not include the current device; instead use it
@ -406,15 +410,16 @@ class Device(Copyable, Mirrorable):
new = Device(name=name, ports={**ports_in, **ports_out}) new = Device(name=name, ports={**ports_in, **ports_out})
return new return new
def plug(self: D, def plug(
other: O, self: D,
map_in: Dict[str, str], other: O,
map_out: Optional[Dict[str, Optional[str]]] = None, map_in: Dict[str, str],
*, map_out: Optional[Dict[str, Optional[str]]] = None,
mirrored: Tuple[bool, bool] = (False, False), *,
inherit_name: bool = True, mirrored: Tuple[bool, bool] = (False, False),
set_rotation: Optional[bool] = None, inherit_name: bool = True,
) -> D: set_rotation: Optional[bool] = None,
) -> D:
""" """
Instantiate the device `other` into the current device, connecting Instantiate the device `other` into the current device, connecting
the ports specified by `map_in` and renaming the unconnected the ports specified by `map_in` and renaming the unconnected
@ -495,16 +500,17 @@ class Device(Copyable, Mirrorable):
mirrored=mirrored, port_map=map_out, skip_port_check=True) mirrored=mirrored, port_map=map_out, skip_port_check=True)
return self return self
def place(self: D, def place(
other: O, self: D,
*, other: O,
offset: vector2 = (0, 0), *,
rotation: float = 0, offset: vector2 = (0, 0),
pivot: vector2 = (0, 0), rotation: float = 0,
mirrored: Tuple[bool, bool] = (False, False), pivot: vector2 = (0, 0),
port_map: Optional[Dict[str, Optional[str]]] = None, mirrored: Tuple[bool, bool] = (False, False),
skip_port_check: bool = False, port_map: Optional[Dict[str, Optional[str]]] = None,
) -> D: skip_port_check: bool = False,
) -> D:
""" """
Instantiate the device `other` into the current device, adding its Instantiate the device `other` into the current device, adding its
ports to those of the current device (but not connecting any ports). ports to those of the current device (but not connecting any ports).
@ -572,13 +578,14 @@ class Device(Copyable, Mirrorable):
self.pattern.subpatterns.append(sp) self.pattern.subpatterns.append(sp)
return self return self
def find_transform(self: D, def find_transform(
other: O, self: D,
map_in: Dict[str, str], other: O,
*, map_in: Dict[str, str],
mirrored: Tuple[bool, bool] = (False, False), *,
set_rotation: Optional[bool] = None, mirrored: Tuple[bool, bool] = (False, False),
) -> Tuple[numpy.ndarray, float, numpy.ndarray]: set_rotation: Optional[bool] = None,
) -> Tuple[numpy.ndarray, float, numpy.ndarray]:
""" """
Given a device `other` and a mapping `map_in` specifying port connections, Given a device `other` and a mapping `map_in` specifying port connections,
find the transform which will correctly align the specified ports. find the transform which will correctly align the specified ports.
@ -745,7 +752,11 @@ class Device(Copyable, Mirrorable):
return s return s
def rotate_offsets_around(offsets: ArrayLike, pivot: ArrayLike, angle: float) -> numpy.ndarray: def rotate_offsets_around(
offsets: ArrayLike,
pivot: ArrayLike,
angle: float,
) -> numpy.ndarray:
offsets -= pivot offsets -= pivot
offsets[:] = (rotation_matrix_2d(angle) @ offsets.T).T offsets[:] = (rotation_matrix_2d(angle) @ offsets.T).T
offsets += pivot offsets += pivot

@ -9,7 +9,8 @@ from ..utils import rotation_matrix_2d, vector2
from ..error import BuildError from ..error import BuildError
def ell(ports: Dict[str, Port], def ell(
ports: Dict[str, Port],
ccw: Optional[bool], ccw: Optional[bool],
bound_type: str, bound_type: str,
bound: Union[float, vector2], bound: Union[float, vector2],

@ -28,13 +28,14 @@ logger.warning('DXF support is experimental and only slightly tested!')
DEFAULT_LAYER = 'DEFAULT' DEFAULT_LAYER = 'DEFAULT'
def write(pattern: Pattern, def write(
stream: io.TextIOBase, pattern: Pattern,
*, stream: io.TextIOBase,
modify_originals: bool = False, *,
dxf_version='AC1024', modify_originals: bool = False,
disambiguate_func: Callable[[Iterable[Pattern]], None] = None, dxf_version='AC1024',
) -> None: disambiguate_func: Callable[[Iterable[Pattern]], None] = None,
) -> None:
""" """
Write a `Pattern` to a DXF file, by first calling `.polygonize()` to change the shapes Write a `Pattern` to a DXF file, by first calling `.polygonize()` to change the shapes
into polygons, and then writing patterns as DXF `Block`s, polygons as `LWPolyline`s, into polygons, and then writing patterns as DXF `Block`s, polygons as `LWPolyline`s,
@ -99,11 +100,12 @@ def write(pattern: Pattern,
lib.write(stream) lib.write(stream)
def writefile(pattern: Pattern, def writefile(
filename: Union[str, pathlib.Path], pattern: Pattern,
*args, filename: Union[str, pathlib.Path],
**kwargs, *args,
) -> None: **kwargs,
) -> None:
""" """
Wrapper for `dxf.write()` that takes a filename or path instead of a stream. Wrapper for `dxf.write()` that takes a filename or path instead of a stream.
@ -125,10 +127,11 @@ def writefile(pattern: Pattern,
write(pattern, stream, *args, **kwargs) write(pattern, stream, *args, **kwargs)
def readfile(filename: Union[str, pathlib.Path], def readfile(
*args, filename: Union[str, pathlib.Path],
**kwargs, *args,
) -> Tuple[Pattern, Dict[str, Any]]: **kwargs,
) -> Tuple[Pattern, Dict[str, Any]]:
""" """
Wrapper for `dxf.read()` that takes a filename or path instead of a stream. Wrapper for `dxf.read()` that takes a filename or path instead of a stream.
@ -150,9 +153,10 @@ def readfile(filename: Union[str, pathlib.Path],
return results return results
def read(stream: io.TextIOBase, def read(
clean_vertices: bool = True, stream: io.TextIOBase,
) -> Tuple[Pattern, Dict[str, Any]]: clean_vertices: bool = True,
) -> Tuple[Pattern, Dict[str, Any]]:
""" """
Read a dxf file and translate it into a dict of `Pattern` objects. DXF `Block`s are Read a dxf file and translate it into a dict of `Pattern` objects. DXF `Block`s are
translated into `Pattern` objects; `LWPolyline`s are translated into polygons, and `Insert`s translated into `Pattern` objects; `LWPolyline`s are translated into polygons, and `Insert`s
@ -273,8 +277,10 @@ def _read_block(block, clean_vertices: bool) -> Pattern:
return pat return pat
def _subpatterns_to_refs(block: Union[ezdxf.layouts.BlockLayout, ezdxf.layouts.Modelspace], def _subpatterns_to_refs(
subpatterns: List[SubPattern]) -> None: block: Union[ezdxf.layouts.BlockLayout, ezdxf.layouts.Modelspace],
subpatterns: List[SubPattern],
) -> None:
for subpat in subpatterns: for subpat in subpatterns:
if subpat.pattern is None: if subpat.pattern is None:
continue continue
@ -318,9 +324,11 @@ def _subpatterns_to_refs(block: Union[ezdxf.layouts.BlockLayout, ezdxf.layouts.M
block.add_blockref(encoded_name, subpat.offset + dd, dxfattribs=attribs) block.add_blockref(encoded_name, subpat.offset + dd, dxfattribs=attribs)
def _shapes_to_elements(block: Union[ezdxf.layouts.BlockLayout, ezdxf.layouts.Modelspace], def _shapes_to_elements(
shapes: List[Shape], block: Union[ezdxf.layouts.BlockLayout, ezdxf.layouts.Modelspace],
polygonize_paths: bool = False): shapes: List[Shape],
polygonize_paths: bool = False,
) -> None:
# Add `LWPolyline`s for each shape. # Add `LWPolyline`s for each shape.
# Could set do paths with width setting, but need to consider endcaps. # Could set do paths with width setting, but need to consider endcaps.
for shape in shapes: for shape in shapes:
@ -331,8 +339,10 @@ def _shapes_to_elements(block: Union[ezdxf.layouts.BlockLayout, ezdxf.layouts.Mo
block.add_lwpolyline(xy_closed, dxfattribs=attribs) block.add_lwpolyline(xy_closed, dxfattribs=attribs)
def _labels_to_texts(block: Union[ezdxf.layouts.BlockLayout, ezdxf.layouts.Modelspace], def _labels_to_texts(
labels: List[Label]) -> None: block: Union[ezdxf.layouts.BlockLayout, ezdxf.layouts.Modelspace],
labels: List[Label],
) -> None:
for label in labels: for label in labels:
attribs = {'layer': _mlayer2dxf(label.layer)} attribs = {'layer': _mlayer2dxf(label.layer)}
xy = label.offset xy = label.offset
@ -349,11 +359,12 @@ def _mlayer2dxf(layer: layer_t) -> str:
raise PatternError(f'Unknown layer type: {layer} ({type(layer)})') raise PatternError(f'Unknown layer type: {layer} ({type(layer)})')
def disambiguate_pattern_names(patterns: Iterable[Pattern], def disambiguate_pattern_names(
max_name_length: int = 32, patterns: Iterable[Pattern],
suffix_length: int = 6, max_name_length: int = 32,
dup_warn_filter: Callable[[str], bool] = None, # If returns False, don't warn about this name suffix_length: int = 6,
) -> None: dup_warn_filter: Callable[[str], bool] = None, # If returns False, don't warn about this name
) -> None:
used_names = [] used_names = []
for pat in patterns: for pat in patterns:
sanitized_name = re.compile(r'[^A-Za-z0-9_\?\$]').sub('_', pat.name) sanitized_name = re.compile(r'[^A-Za-z0-9_\?\$]').sub('_', pat.name)

@ -52,15 +52,16 @@ path_cap_map = {
} }
def write(patterns: Union[Pattern, Sequence[Pattern]], def write(
stream: BinaryIO, patterns: Union[Pattern, Sequence[Pattern]],
meters_per_unit: float, stream: BinaryIO,
logical_units_per_unit: float = 1, meters_per_unit: float,
library_name: str = 'masque-klamath', logical_units_per_unit: float = 1,
*, library_name: str = 'masque-klamath',
modify_originals: bool = False, *,
disambiguate_func: Callable[[Iterable[Pattern]], None] = None, modify_originals: bool = False,
) -> None: disambiguate_func: Callable[[Iterable[Pattern]], None] = None,
) -> None:
""" """
Convert a `Pattern` or list of patterns to a GDSII stream, and then mapping data as follows: Convert a `Pattern` or list of patterns to a GDSII stream, and then mapping data as follows:
Pattern -> GDSII structure Pattern -> GDSII structure
@ -136,11 +137,12 @@ def write(patterns: Union[Pattern, Sequence[Pattern]],
records.ENDLIB.write(stream, None) records.ENDLIB.write(stream, None)
def writefile(patterns: Union[Sequence[Pattern], Pattern], def writefile(
filename: Union[str, pathlib.Path], patterns: Union[Sequence[Pattern], Pattern],
*args, filename: Union[str, pathlib.Path],
**kwargs, *args,
) -> None: **kwargs,
) -> None:
""" """
Wrapper for `write()` that takes a filename or path instead of a stream. Wrapper for `write()` that takes a filename or path instead of a stream.
@ -162,10 +164,11 @@ def writefile(patterns: Union[Sequence[Pattern], Pattern],
write(patterns, stream, *args, **kwargs) write(patterns, stream, *args, **kwargs)
def readfile(filename: Union[str, pathlib.Path], def readfile(
*args, filename: Union[str, pathlib.Path],
**kwargs, *args,
) -> Tuple[Dict[str, Pattern], Dict[str, Any]]: **kwargs,
) -> Tuple[Dict[str, Pattern], Dict[str, Any]]:
""" """
Wrapper for `read()` that takes a filename or path instead of a stream. Wrapper for `read()` that takes a filename or path instead of a stream.
@ -187,9 +190,10 @@ def readfile(filename: Union[str, pathlib.Path],
return results return results
def read(stream: BinaryIO, def read(
raw_mode: bool = True, stream: BinaryIO,
) -> Tuple[Dict[str, Pattern], Dict[str, Any]]: raw_mode: bool = True,
) -> Tuple[Dict[str, Pattern], Dict[str, Any]]:
""" """
Read a gdsii file and translate it into a dict of Pattern objects. GDSII structures are Read a gdsii file and translate it into a dict of Pattern objects. GDSII structures are
translated into Pattern objects; boundaries are translated into polygons, and srefs and arefs translated into Pattern objects; boundaries are translated into polygons, and srefs and arefs
@ -243,10 +247,11 @@ def _read_header(stream: BinaryIO) -> Dict[str, Any]:
return library_info return library_info
def read_elements(stream: BinaryIO, def read_elements(
name: str, stream: BinaryIO,
raw_mode: bool = True, name: str,
) -> Pattern: raw_mode: bool = True,
) -> Pattern:
""" """
Read elements from a GDS structure and build a Pattern from them. Read elements from a GDS structure and build a Pattern from them.
@ -296,8 +301,7 @@ def _mlayer2gds(mlayer: layer_t) -> Tuple[int, int]:
return layer, data_type return layer, data_type
def _ref_to_subpat(ref: klamath.library.Reference, def _ref_to_subpat(ref: klamath.library.Reference) -> SubPattern:
) -> SubPattern:
""" """
Helper function to create a SubPattern from an SREF or AREF. Sets subpat.pattern to None Helper function to create a SubPattern from an SREF or AREF. Sets subpat.pattern to None
and sets the instance .identifier to (struct_name,). and sets the instance .identifier to (struct_name,).
@ -351,8 +355,7 @@ def _boundary_to_polygon(boundary: klamath.library.Boundary, raw_mode: bool) ->
) )
def _subpatterns_to_refs(subpatterns: List[SubPattern] def _subpatterns_to_refs(subpatterns: List[SubPattern]) -> List[klamath.library.Reference]:
) -> List[klamath.library.Reference]:
refs = [] refs = []
for subpat in subpatterns: for subpat in subpatterns:
if subpat.pattern is None: if subpat.pattern is None:
@ -427,9 +430,10 @@ def _annotations_to_properties(annotations: annotations_t, max_len: int = 126) -
return props return props
def _shapes_to_elements(shapes: List[Shape], def _shapes_to_elements(
polygonize_paths: bool = False shapes: List[Shape],
) -> List[klamath.elements.Element]: polygonize_paths: bool = False,
) -> List[klamath.elements.Element]:
elements: List[klamath.elements.Element] = [] elements: List[klamath.elements.Element] = []
# Add a Boundary element for each shape, and Path elements if necessary # Add a Boundary element for each shape, and Path elements if necessary
for shape in shapes: for shape in shapes:
@ -492,11 +496,12 @@ def _labels_to_texts(labels: List[Label]) -> List[klamath.elements.Text]:
return texts return texts
def disambiguate_pattern_names(patterns: Sequence[Pattern], def disambiguate_pattern_names(
max_name_length: int = 32, patterns: Sequence[Pattern],
suffix_length: int = 6, max_name_length: int = 32,
dup_warn_filter: Optional[Callable[[str], bool]] = None, suffix_length: int = 6,
): dup_warn_filter: Optional[Callable[[str], bool]] = None,
) -> None:
""" """
Args: Args:
patterns: List of patterns to disambiguate patterns: List of patterns to disambiguate
@ -549,12 +554,13 @@ def disambiguate_pattern_names(patterns: Sequence[Pattern],
used_names.append(suffixed_name) used_names.append(suffixed_name)
def load_library(stream: BinaryIO, def load_library(
tag: str, stream: BinaryIO,
is_secondary: Optional[Callable[[str], bool]] = None, tag: str,
*, is_secondary: Optional[Callable[[str], bool]] = None,
full_load: bool = False, *,
) -> Tuple[Library, Dict[str, Any]]: full_load: bool = False,
) -> Tuple[Library, Dict[str, Any]]:
""" """
Scan a GDSII stream to determine what structures are present, and create Scan a GDSII stream to determine what structures are present, and create
a library from them. This enables deferred reading of structures a library from them. This enables deferred reading of structures
@ -581,7 +587,7 @@ def load_library(stream: BinaryIO,
Additional library info (dict, same format as from `read`). Additional library info (dict, same format as from `read`).
""" """
if is_secondary is None: if is_secondary is None:
def is_secondary(k: str): def is_secondary(k: str) -> bool:
return False return False
assert(is_secondary is not None) assert(is_secondary is not None)
@ -611,13 +617,14 @@ def load_library(stream: BinaryIO,
return lib, library_info return lib, library_info
def load_libraryfile(filename: Union[str, pathlib.Path], def load_libraryfile(
tag: str, filename: Union[str, pathlib.Path],
is_secondary: Optional[Callable[[str], bool]] = None, tag: str,
*, is_secondary: Optional[Callable[[str], bool]] = None,
use_mmap: bool = True, *,
full_load: bool = False, use_mmap: bool = True,
) -> Tuple[Library, Dict[str, Any]]: full_load: bool = False,
) -> Tuple[Library, Dict[str, Any]]:
""" """
Wrapper for `load_library()` that takes a filename or path instead of a stream. Wrapper for `load_library()` that takes a filename or path instead of a stream.

@ -47,14 +47,15 @@ path_cap_map = {
#TODO implement more shape types? #TODO implement more shape types?
def build(patterns: Union[Pattern, Sequence[Pattern]], def build(
units_per_micron: int, patterns: Union[Pattern, Sequence[Pattern]],
layer_map: Optional[Dict[str, Union[int, Tuple[int, int]]]] = None, units_per_micron: int,
*, layer_map: Optional[Dict[str, Union[int, Tuple[int, int]]]] = None,
modify_originals: bool = False, *,
disambiguate_func: Optional[Callable[[Iterable[Pattern]], None]] = None, modify_originals: bool = False,
annotations: Optional[annotations_t] = None disambiguate_func: Optional[Callable[[Iterable[Pattern]], None]] = None,
) -> fatamorgana.OasisLayout: annotations: Optional[annotations_t] = None,
) -> fatamorgana.OasisLayout:
""" """
Convert a `Pattern` or list of patterns to an OASIS stream, writing patterns Convert a `Pattern` or list of patterns to an OASIS stream, writing patterns
as OASIS cells, subpatterns as Placement records, and other shapes and labels as OASIS cells, subpatterns as Placement records, and other shapes and labels
@ -153,10 +154,12 @@ def build(patterns: Union[Pattern, Sequence[Pattern]],
return lib return lib
def write(patterns: Union[Sequence[Pattern], Pattern], def write(
stream: io.BufferedIOBase, patterns: Union[Sequence[Pattern], Pattern],
*args, stream: io.BufferedIOBase,
**kwargs): *args,
**kwargs,
) -> None:
""" """
Write a `Pattern` or list of patterns to a OASIS file. See `oasis.build()` Write a `Pattern` or list of patterns to a OASIS file. See `oasis.build()`
for details. for details.
@ -171,11 +174,12 @@ def write(patterns: Union[Sequence[Pattern], Pattern],
lib.write(stream) lib.write(stream)
def writefile(patterns: Union[Sequence[Pattern], Pattern], def writefile(
filename: Union[str, pathlib.Path], patterns: Union[Sequence[Pattern], Pattern],
*args, filename: Union[str, pathlib.Path],
**kwargs, *args,
): **kwargs,
) -> None:
""" """
Wrapper for `oasis.write()` that takes a filename or path instead of a stream. Wrapper for `oasis.write()` that takes a filename or path instead of a stream.
@ -198,10 +202,11 @@ def writefile(patterns: Union[Sequence[Pattern], Pattern],
return results return results
def readfile(filename: Union[str, pathlib.Path], def readfile(
*args, filename: Union[str, pathlib.Path],
**kwargs, *args,
) -> Tuple[Dict[str, Pattern], Dict[str, Any]]: **kwargs,
) -> Tuple[Dict[str, Pattern], Dict[str, Any]]:
""" """
Wrapper for `oasis.read()` that takes a filename or path instead of a stream. Wrapper for `oasis.read()` that takes a filename or path instead of a stream.
@ -223,9 +228,10 @@ def readfile(filename: Union[str, pathlib.Path],
return results return results
def read(stream: io.BufferedIOBase, def read(
clean_vertices: bool = True, stream: io.BufferedIOBase,
) -> Tuple[Dict[str, Pattern], Dict[str, Any]]: clean_vertices: bool = True,
) -> Tuple[Dict[str, Pattern], Dict[str, Any]]:
""" """
Read a OASIS file and translate it into a dict of Pattern objects. OASIS cells are Read a OASIS file and translate it into a dict of Pattern objects. OASIS cells are
translated into Pattern objects; Polygons are translated into polygons, and Placements translated into Pattern objects; Polygons are translated into polygons, and Placements
@ -496,8 +502,9 @@ def _placement_to_subpat(placement: fatrec.Placement, lib: fatamorgana.OasisLayo
return subpat return subpat
def _subpatterns_to_placements(subpatterns: List[SubPattern] def _subpatterns_to_placements(
) -> List[fatrec.Placement]: subpatterns: List[SubPattern],
) -> List[fatrec.Placement]:
refs = [] refs = []
for subpat in subpatterns: for subpat in subpatterns:
if subpat.pattern is None: if subpat.pattern is None:
@ -523,9 +530,10 @@ def _subpatterns_to_placements(subpatterns: List[SubPattern]
return refs return refs
def _shapes_to_elements(shapes: List[Shape], def _shapes_to_elements(
layer2oas: Callable[[layer_t], Tuple[int, int]], shapes: List[Shape],
) -> List[Union[fatrec.Polygon, fatrec.Path, fatrec.Circle]]: layer2oas: Callable[[layer_t], Tuple[int, int]],
) -> List[Union[fatrec.Polygon, fatrec.Path, fatrec.Circle]]:
# Add a Polygon record for each shape, and Path elements if necessary # Add a Polygon record for each shape, and Path elements if necessary
elements: List[Union[fatrec.Polygon, fatrec.Path, fatrec.Circle]] = [] elements: List[Union[fatrec.Polygon, fatrec.Path, fatrec.Circle]] = []
for shape in shapes: for shape in shapes:
@ -576,9 +584,10 @@ def _shapes_to_elements(shapes: List[Shape],
return elements return elements
def _labels_to_texts(labels: List[Label], def _labels_to_texts(
layer2oas: Callable[[layer_t], Tuple[int, int]], labels: List[Label],
) -> List[fatrec.Text]: layer2oas: Callable[[layer_t], Tuple[int, int]],
) -> List[fatrec.Text]:
texts = [] texts = []
for label in labels: for label in labels:
layer, datatype = layer2oas(label.layer) layer, datatype = layer2oas(label.layer)
@ -595,9 +604,10 @@ def _labels_to_texts(labels: List[Label],
return texts return texts
def disambiguate_pattern_names(patterns, def disambiguate_pattern_names(
dup_warn_filter: Callable[[str], bool] = None, # If returns False, don't warn about this name patterns,
): dup_warn_filter: Callable[[str], bool] = None, # If returns False, don't warn about this name
) -> None:
used_names = [] used_names = []
for pat in patterns: for pat in patterns:
sanitized_name = re.compile(r'[^A-Za-z0-9_\?\$]').sub('_', pat.name) sanitized_name = re.compile(r'[^A-Za-z0-9_\?\$]').sub('_', pat.name)
@ -625,8 +635,9 @@ def disambiguate_pattern_names(patterns,
used_names.append(suffixed_name) used_names.append(suffixed_name)
def repetition_fata2masq(rep: Union[fatamorgana.GridRepetition, fatamorgana.ArbitraryRepetition, None] def repetition_fata2masq(
) -> Optional[Repetition]: rep: Union[fatamorgana.GridRepetition, fatamorgana.ArbitraryRepetition, None],
) -> Optional[Repetition]:
mrep: Optional[Repetition] mrep: Optional[Repetition]
if isinstance(rep, fatamorgana.GridRepetition): if isinstance(rep, fatamorgana.GridRepetition):
mrep = Grid(a_vector=rep.a_vector, mrep = Grid(a_vector=rep.a_vector,
@ -643,11 +654,12 @@ def repetition_fata2masq(rep: Union[fatamorgana.GridRepetition, fatamorgana.Arbi
return mrep return mrep
def repetition_masq2fata(rep: Optional[Repetition] def repetition_masq2fata(
) -> Tuple[Union[fatamorgana.GridRepetition, rep: Optional[Repetition],
fatamorgana.ArbitraryRepetition, ) -> Tuple[Union[fatamorgana.GridRepetition,
None], fatamorgana.ArbitraryRepetition,
Tuple[int, int]]: None],
Tuple[int, int]]:
frep: Union[fatamorgana.GridRepetition, fatamorgana.ArbitraryRepetition, None] frep: Union[fatamorgana.GridRepetition, fatamorgana.ArbitraryRepetition, None]
if isinstance(rep, Grid): if isinstance(rep, Grid):
frep = fatamorgana.GridRepetition( frep = fatamorgana.GridRepetition(
@ -678,10 +690,11 @@ def annotations_to_properties(annotations: annotations_t) -> List[fatrec.Propert
return properties return properties
def properties_to_annotations(properties: List[fatrec.Property], def properties_to_annotations(
propnames: Dict[int, NString], properties: List[fatrec.Property],
propstrings: Dict[int, AString], propnames: Dict[int, NString],
) -> annotations_t: propstrings: Dict[int, AString],
) -> annotations_t:
annotations = {} annotations = {}
for proprec in properties: for proprec in properties:
assert(proprec.name is not None) assert(proprec.name is not None)

@ -53,14 +53,15 @@ path_cap_map = {
} }
def build(patterns: Union[Pattern, Sequence[Pattern]], def build(
meters_per_unit: float, patterns: Union[Pattern, Sequence[Pattern]],
logical_units_per_unit: float = 1, meters_per_unit: float,
library_name: str = 'masque-gdsii-write', logical_units_per_unit: float = 1,
*, library_name: str = 'masque-gdsii-write',
modify_originals: bool = False, *,
disambiguate_func: Callable[[Iterable[Pattern]], None] = None, modify_originals: bool = False,
) -> gdsii.library.Library: disambiguate_func: Callable[[Iterable[Pattern]], None] = None,
) -> gdsii.library.Library:
""" """
Convert a `Pattern` or list of patterns to a GDSII stream, by first calling Convert a `Pattern` or list of patterns to a GDSII stream, 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
@ -137,10 +138,12 @@ def build(patterns: Union[Pattern, Sequence[Pattern]],
return lib return lib
def write(patterns: Union[Pattern, Sequence[Pattern]], def write(
stream: io.BufferedIOBase, patterns: Union[Pattern, Sequence[Pattern]],
*args, stream: io.BufferedIOBase,
**kwargs): *args,
**kwargs,
) -> None:
""" """
Write a `Pattern` or list of patterns to a GDSII file. Write a `Pattern` or list of patterns to a GDSII file.
See `masque.file.gdsii.build()` for details. See `masque.file.gdsii.build()` for details.
@ -155,11 +158,12 @@ def write(patterns: Union[Pattern, Sequence[Pattern]],
lib.save(stream) lib.save(stream)
return return
def writefile(patterns: Union[Sequence[Pattern], Pattern], def writefile(
filename: Union[str, pathlib.Path], patterns: Union[Sequence[Pattern], Pattern],
*args, filename: Union[str, pathlib.Path],
**kwargs, *args,
): **kwargs,
) -> None:
""" """
Wrapper for `masque.file.gdsii.write()` that takes a filename or path instead of a stream. Wrapper for `masque.file.gdsii.write()` that takes a filename or path instead of a stream.
@ -182,10 +186,11 @@ def writefile(patterns: Union[Sequence[Pattern], Pattern],
return results return results
def readfile(filename: Union[str, pathlib.Path], def readfile(
*args, filename: Union[str, pathlib.Path],
**kwargs, *args,
) -> Tuple[Dict[str, Pattern], Dict[str, Any]]: **kwargs,
) -> Tuple[Dict[str, Pattern], Dict[str, Any]]:
""" """
Wrapper for `masque.file.gdsii.read()` that takes a filename or path instead of a stream. Wrapper for `masque.file.gdsii.read()` that takes a filename or path instead of a stream.
@ -207,9 +212,10 @@ def readfile(filename: Union[str, pathlib.Path],
return results return results
def read(stream: io.BufferedIOBase, def read(
clean_vertices: bool = True, stream: io.BufferedIOBase,
) -> Tuple[Dict[str, Pattern], Dict[str, Any]]: clean_vertices: bool = True,
) -> Tuple[Dict[str, Pattern], Dict[str, Any]]:
""" """
Read a gdsii file and translate it into a dict of Pattern objects. GDSII structures are Read a gdsii file and translate it into a dict of Pattern objects. GDSII structures are
translated into Pattern objects; boundaries are translated into polygons, and srefs and arefs translated into Pattern objects; boundaries are translated into polygons, and srefs and arefs
@ -294,9 +300,10 @@ def _mlayer2gds(mlayer: layer_t) -> Tuple[int, int]:
return layer, data_type return layer, data_type
def _ref_to_subpat(element: Union[gdsii.elements.SRef, def _ref_to_subpat(
gdsii.elements.ARef] element: Union[gdsii.elements.SRef,
) -> SubPattern: gdsii.elements.ARef]
) -> SubPattern:
""" """
Helper function to create a SubPattern from an SREF or AREF. Sets subpat.pattern to None Helper function to create a SubPattern from an SREF or AREF. Sets subpat.pattern to None
and sets the instance .identifier to (struct_name,). and sets the instance .identifier to (struct_name,).
@ -379,8 +386,9 @@ def _boundary_to_polygon(element: gdsii.elements.Boundary, raw_mode: bool) -> Po
return Polygon(**args) return Polygon(**args)
def _subpatterns_to_refs(subpatterns: List[SubPattern] def _subpatterns_to_refs(
) -> List[Union[gdsii.elements.ARef, gdsii.elements.SRef]]: subpatterns: List[SubPattern],
) -> List[Union[gdsii.elements.ARef, gdsii.elements.SRef]]:
refs = [] refs = []
for subpat in subpatterns: for subpat in subpatterns:
if subpat.pattern is None: if subpat.pattern is None:
@ -450,9 +458,10 @@ def _annotations_to_properties(annotations: annotations_t, max_len: int = 126) -
return props return props
def _shapes_to_elements(shapes: List[Shape], def _shapes_to_elements(
polygonize_paths: bool = False shapes: List[Shape],
) -> List[Union[gdsii.elements.Boundary, gdsii.elements.Path]]: polygonize_paths: bool = False,
) -> List[Union[gdsii.elements.Boundary, gdsii.elements.Path]]:
elements: List[Union[gdsii.elements.Boundary, gdsii.elements.Path]] = [] elements: List[Union[gdsii.elements.Boundary, gdsii.elements.Path]] = []
# Add a Boundary element for each shape, and Path elements if necessary # Add a Boundary element for each shape, and Path elements if necessary
for shape in shapes: for shape in shapes:
@ -496,11 +505,12 @@ def _labels_to_texts(labels: List[Label]) -> List[gdsii.elements.Text]:
return texts return texts
def disambiguate_pattern_names(patterns: Sequence[Pattern], def disambiguate_pattern_names(
max_name_length: int = 32, patterns: Sequence[Pattern],
suffix_length: int = 6, max_name_length: int = 32,
dup_warn_filter: Optional[Callable[[str], bool]] = None, suffix_length: int = 6,
): dup_warn_filter: Optional[Callable[[str], bool]] = None,
) -> None:
""" """
Args: Args:
patterns: List of patterns to disambiguate patterns: List of patterns to disambiguate

@ -11,10 +11,11 @@ from .utils import mangle_name
from .. import Pattern from .. import Pattern
def writefile(pattern: Pattern, def writefile(
filename: str, pattern: Pattern,
custom_attributes: bool = False, filename: str,
) -> None: custom_attributes: bool = False,
) -> None:
""" """
Write a Pattern to an SVG file, by first calling .polygonize() on it Write a Pattern to an SVG file, by first calling .polygonize() on it
to change the shapes into polygons, and then writing patterns as SVG to change the shapes into polygons, and then writing patterns as SVG

@ -95,8 +95,9 @@ def dtype2dose(pattern: Pattern) -> Pattern:
return pattern return pattern
def dose2dtype(patterns: List[Pattern], def dose2dtype(
) -> Tuple[List[Pattern], List[float]]: patterns: List[Pattern],
) -> Tuple[List[Pattern], List[float]]:
""" """
For each shape in each pattern, set shape.layer to the tuple For each shape in each pattern, set shape.layer to the tuple
(base_layer, datatype), where: (base_layer, datatype), where:

@ -36,19 +36,20 @@ class Label(PositionableImpl, LayerableImpl, LockableImpl, RepeatableImpl, Annot
return self._string return self._string
@string.setter @string.setter
def string(self, val: str): def string(self, val: str) -> None:
self._string = val self._string = val
def __init__(self, def __init__(
string: str, self,
*, string: str,
offset: vector2 = (0.0, 0.0), *,
layer: layer_t = 0, offset: vector2 = (0.0, 0.0),
repetition: Optional[Repetition] = None, layer: layer_t = 0,
annotations: Optional[annotations_t] = None, repetition: Optional[Repetition] = None,
locked: bool = False, annotations: Optional[annotations_t] = None,
identifier: Tuple = (), locked: bool = False,
) -> None: identifier: Tuple = (),
) -> None:
LockableImpl.unlock(self) LockableImpl.unlock(self)
self.identifier = identifier self.identifier = identifier
self.string = string self.string = string

@ -143,7 +143,13 @@ class Library:
def __repr__(self) -> str: def __repr__(self) -> str:
return '<Library with keys ' + repr(list(self.primary.keys())) + '>' return '<Library with keys ' + repr(list(self.primary.keys())) + '>'
def set_const(self, key: str, tag: Any, const: 'Pattern', secondary: bool = False) -> None: def set_const(
self,
key: str,
tag: Any,
const: 'Pattern',
secondary: bool = False,
) -> None:
""" """
Convenience function to avoid having to manually wrap Convenience function to avoid having to manually wrap
constant values into callables. constant values into callables.
@ -162,7 +168,13 @@ class Library:
else: else:
self.primary[key] = pg self.primary[key] = pg
def set_value(self, key: str, tag: str, value: Callable[[], 'Pattern'], secondary: bool = False) -> None: def set_value(
self,
key: str,
tag: str,
value: Callable[[], 'Pattern'],
secondary: bool = False,
) -> None:
""" """
Convenience function to automatically build a PatternGenerator. Convenience function to automatically build a PatternGenerator.

@ -53,15 +53,16 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
name: str name: str
""" A name for this pattern """ """ A name for this pattern """
def __init__(self, def __init__(
name: str = '', self,
*, name: str = '',
shapes: Sequence[Shape] = (), *,
labels: Sequence[Label] = (), shapes: Sequence[Shape] = (),
subpatterns: Sequence[SubPattern] = (), labels: Sequence[Label] = (),
annotations: Optional[annotations_t] = None, subpatterns: Sequence[SubPattern] = (),
locked: bool = False, annotations: Optional[annotations_t] = None,
) -> None: locked: bool = False,
) -> None:
""" """
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.
@ -141,12 +142,13 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
self.labels += other_pattern.labels self.labels += other_pattern.labels
return self return self
def subset(self, def subset(
shapes_func: Callable[[Shape], bool] = None, self,
labels_func: Callable[[Label], bool] = None, shapes_func: Callable[[Shape], bool] = None,
subpatterns_func: Callable[[SubPattern], bool] = None, labels_func: Callable[[Label], bool] = None,
recursive: bool = False, subpatterns_func: Callable[[SubPattern], bool] = None,
) -> 'Pattern': recursive: bool = False,
) -> 'Pattern':
""" """
Returns a Pattern containing only the entities (e.g. shapes) for which the Returns a Pattern containing only the entities (e.g. shapes) for which the
given entity_func returns True. given entity_func returns True.
@ -186,10 +188,11 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
assert(pat is not None) assert(pat is not None)
return pat return pat
def apply(self, def apply(
func: Callable[[Optional['Pattern']], Optional['Pattern']], self,
memo: Optional[Dict[int, Optional['Pattern']]] = None, func: Callable[[Optional['Pattern']], Optional['Pattern']],
) -> Optional['Pattern']: memo: Optional[Dict[int, Optional['Pattern']]] = None,
) -> Optional['Pattern']:
""" """
Recursively apply func() to this pattern and any pattern it references. Recursively apply func() to this pattern and any pattern it references.
func() is expected to take and return a Pattern. func() is expected to take and return a Pattern.
@ -229,7 +232,8 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
pat = memo[pat_id] pat = memo[pat_id]
return pat return pat
def dfs(self: P, def dfs(
self: P,
visit_before: visitor_function_t = None, visit_before: visitor_function_t = None,
visit_after: visitor_function_t = None, visit_after: visitor_function_t = None,
transform: Union[numpy.ndarray, bool, None] = False, transform: Union[numpy.ndarray, bool, None] = False,
@ -237,7 +241,7 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
hierarchy: Tuple[P, ...] = (), hierarchy: Tuple[P, ...] = (),
) -> P: ) -> P:
""" """
Experimental convenience function. Convenience function.
Performs a depth-first traversal of this pattern and its subpatterns. Performs a depth-first traversal of this pattern and its subpatterns.
At each pattern in the tree, the following sequence is called: At each pattern in the tree, the following sequence is called:
``` ```
@ -314,10 +318,11 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
pat = visit_after(pat, hierarchy=hierarchy, memo=memo, transform=transform) # type: ignore pat = visit_after(pat, hierarchy=hierarchy, memo=memo, transform=transform) # type: ignore
return pat return pat
def polygonize(self: P, def polygonize(
poly_num_points: Optional[int] = None, self: P,
poly_max_arclen: Optional[float] = None, poly_num_points: Optional[int] = None,
) -> P: poly_max_arclen: Optional[float] = None,
) -> P:
""" """
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.
@ -342,10 +347,11 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
subpat.pattern.polygonize(poly_num_points, poly_max_arclen) subpat.pattern.polygonize(poly_num_points, poly_max_arclen)
return self return self
def manhattanize(self: P, def manhattanize(
grid_x: ArrayLike, self: P,
grid_y: ArrayLike, grid_x: ArrayLike,
) -> P: grid_y: ArrayLike,
) -> P:
""" """
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.
@ -364,11 +370,12 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
(shape.manhattanize(grid_x, grid_y) for shape in old_shapes))) (shape.manhattanize(grid_x, grid_y) for shape in old_shapes)))
return self return self
def subpatternize(self: P, def subpatternize(
recursive: bool = True, self: P,
norm_value: int = int(1e6), recursive: bool = True,
exclude_types: Tuple[Type] = (Polygon,) norm_value: int = int(1e6),
) -> P: exclude_types: Tuple[Type] = (Polygon,)
) -> P:
""" """
Iterates through this `Pattern` and all referenced `Pattern`s. 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-,
@ -456,11 +463,12 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
def referenced_patterns_by_id(self, include_none: bool) -> Dict[int, Optional['Pattern']]: def referenced_patterns_by_id(self, include_none: bool) -> Dict[int, Optional['Pattern']]:
pass pass
def referenced_patterns_by_id(self, def referenced_patterns_by_id(
include_none: bool = False, self,
recursive: bool = True, include_none: bool = False,
) -> Union[Dict[int, Optional['Pattern']], recursive: bool = True,
Dict[int, 'Pattern']]: ) -> Union[Dict[int, Optional['Pattern']],
Dict[int, 'Pattern']]:
""" """
Create a dictionary with `{id(pat): pat}` for all Pattern objects referenced by this Create a dictionary with `{id(pat): pat}` for all Pattern objects referenced by this
@ -484,7 +492,10 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
ids.update(pat.referenced_patterns_by_id()) ids.update(pat.referenced_patterns_by_id())
return ids return ids
def referenced_patterns_by_name(self, **kwargs: Any) -> List[Tuple[Optional[str], Optional['Pattern']]]: def referenced_patterns_by_name(
self,
**kwargs: Any,
) -> List[Tuple[Optional[str], Optional['Pattern']]]:
""" """
Create a list of `(pat.name, pat)` tuples for all Pattern objects referenced by this Create a list of `(pat.name, pat)` tuples 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).
@ -502,10 +513,11 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
pat_list = [(p.name if p is not None else None, p) for p in pats_by_id.values()] pat_list = [(p.name if p is not None else None, p) for p in pats_by_id.values()]
return pat_list return pat_list
def subpatterns_by_id(self, def subpatterns_by_id(
include_none: bool = False, self,
recursive: bool = True, include_none: bool = False,
) -> Dict[int, List[SubPattern]]: recursive: bool = True,
) -> Dict[int, List[SubPattern]]:
""" """
Create a dictionary which maps `{id(referenced_pattern): [subpattern0, ...]}` Create a dictionary which maps `{id(referenced_pattern): [subpattern0, ...]}`
for all SubPattern objects referenced by this Pattern (by default, operates for all SubPattern objects referenced by this Pattern (by default, operates
@ -593,10 +605,11 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
flatten_single(self, set()) flatten_single(self, set())
return self return self
def wrap_repeated_shapes(self: P, def wrap_repeated_shapes(
name_func: Callable[['Pattern', Union[Shape, Label]], str] = lambda p, s: '_repetition', self: P,
recursive: bool = True, name_func: Callable[['Pattern', Union[Shape, Label]], str] = lambda p, s: '_repetition',
) -> P: recursive: bool = True,
) -> P:
""" """
Wraps all shapes and labels with a non-`None` `repetition` attribute Wraps all shapes and labels with a non-`None` `repetition` attribute
into a `SubPattern`/`Pattern` combination, and applies the `repetition` into a `SubPattern`/`Pattern` combination, and applies the `repetition`
@ -930,12 +943,13 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
pickle.dump(self, f, protocol=pickle.HIGHEST_PROTOCOL) pickle.dump(self, f, protocol=pickle.HIGHEST_PROTOCOL)
return self return self
def visualize(self, def visualize(
offset: vector2 = (0., 0.), self,
line_color: str = 'k', offset: vector2 = (0., 0.),
fill_color: str = 'none', line_color: str = 'k',
overdraw: bool = False, fill_color: str = 'none',
) -> None: overdraw: bool = False,
) -> None:
""" """
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

@ -61,12 +61,14 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
_b_count: int _b_count: int
""" Number of instances along the direction specified by the `b_vector` """ """ Number of instances along the direction specified by the `b_vector` """
def __init__(self, def __init__(
a_vector: ArrayLike, self,
a_count: int, a_vector: ArrayLike,
b_vector: Optional[ArrayLike] = None, a_count: int,
b_count: Optional[int] = 1, b_vector: Optional[ArrayLike] = None,
locked: bool = False,): b_count: Optional[int] = 1,
locked: bool = False,
) -> None:
""" """
Args: Args:
a_vector: First lattice vector, of the form `[x, y]`. a_vector: First lattice vector, of the form `[x, y]`.

@ -51,7 +51,7 @@ class Arc(Shape, metaclass=AutoSlots):
return self._radii return self._radii
@radii.setter @radii.setter
def radii(self, val: vector2): def radii(self, val: vector2) -> None:
val = numpy.array(val, dtype=float).flatten() val = numpy.array(val, dtype=float).flatten()
if not val.size == 2: if not val.size == 2:
raise PatternError('Radii must have length 2') raise PatternError('Radii must have length 2')
@ -64,7 +64,7 @@ class Arc(Shape, metaclass=AutoSlots):
return self._radii[0] return self._radii[0]
@radius_x.setter @radius_x.setter
def radius_x(self, val: float): def radius_x(self, val: float) -> None:
if not val >= 0: if not val >= 0:
raise PatternError('Radius must be non-negative') raise PatternError('Radius must be non-negative')
self._radii[0] = val self._radii[0] = val
@ -74,7 +74,7 @@ class Arc(Shape, metaclass=AutoSlots):
return self._radii[1] return self._radii[1]
@radius_y.setter @radius_y.setter
def radius_y(self, val: float): def radius_y(self, val: float) -> None:
if not val >= 0: if not val >= 0:
raise PatternError('Radius must be non-negative') raise PatternError('Radius must be non-negative')
self._radii[1] = val self._radii[1] = val
@ -92,7 +92,7 @@ class Arc(Shape, metaclass=AutoSlots):
return self._angles return self._angles
@angles.setter @angles.setter
def angles(self, val: vector2): def angles(self, val: vector2) -> None:
val = numpy.array(val, dtype=float).flatten() val = numpy.array(val, dtype=float).flatten()
if not val.size == 2: if not val.size == 2:
raise PatternError('Angles must have length 2') raise PatternError('Angles must have length 2')
@ -103,7 +103,7 @@ class Arc(Shape, metaclass=AutoSlots):
return self.angles[0] return self.angles[0]
@start_angle.setter @start_angle.setter
def start_angle(self, val: float): def start_angle(self, val: float) -> None:
self.angles = (val, self.angles[1]) self.angles = (val, self.angles[1])
@property @property
@ -111,7 +111,7 @@ class Arc(Shape, metaclass=AutoSlots):
return self.angles[1] return self.angles[1]
@stop_angle.setter @stop_angle.setter
def stop_angle(self, val: float): def stop_angle(self, val: float) -> None:
self.angles = (self.angles[0], val) self.angles = (self.angles[0], val)
# Rotation property # Rotation property
@ -126,7 +126,7 @@ class Arc(Shape, metaclass=AutoSlots):
return self._rotation return self._rotation
@rotation.setter @rotation.setter
def rotation(self, val: float): def rotation(self, val: float) -> None:
if not is_scalar(val): if not is_scalar(val):
raise PatternError('Rotation must be a scalar') raise PatternError('Rotation must be a scalar')
self._rotation = val % (2 * pi) self._rotation = val % (2 * pi)
@ -143,30 +143,31 @@ class Arc(Shape, metaclass=AutoSlots):
return self._width return self._width
@width.setter @width.setter
def width(self, val: float): def width(self, val: float) -> None:
if not is_scalar(val): if not is_scalar(val):
raise PatternError('Width must be a scalar') raise PatternError('Width must be a scalar')
if not val > 0: if not val > 0:
raise PatternError('Width must be positive') raise PatternError('Width must be positive')
self._width = val self._width = val
def __init__(self, def __init__(
radii: vector2, self,
angles: vector2, radii: vector2,
width: float, angles: vector2,
*, width: float,
poly_num_points: Optional[int] = DEFAULT_POLY_NUM_POINTS, *,
poly_max_arclen: Optional[float] = None, poly_num_points: Optional[int] = DEFAULT_POLY_NUM_POINTS,
offset: vector2 = (0.0, 0.0), poly_max_arclen: Optional[float] = None,
rotation: float = 0, offset: vector2 = (0.0, 0.0),
mirrored: Sequence[bool] = (False, False), rotation: float = 0,
layer: layer_t = 0, mirrored: Sequence[bool] = (False, False),
dose: float = 1.0, layer: layer_t = 0,
repetition: Optional[Repetition] = None, dose: float = 1.0,
annotations: Optional[annotations_t] = None, repetition: Optional[Repetition] = None,
locked: bool = False, annotations: Optional[annotations_t] = None,
raw: bool = False, locked: bool = False,
): raw: bool = False,
) -> None:
LockableImpl.unlock(self) LockableImpl.unlock(self)
self.identifier = () self.identifier = ()
if raw: if raw:
@ -204,10 +205,11 @@ class Arc(Shape, metaclass=AutoSlots):
new.set_locked(self.locked) new.set_locked(self.locked)
return new return new
def to_polygons(self, def to_polygons(
poly_num_points: Optional[int] = None, self,
poly_max_arclen: Optional[float] = None, poly_num_points: Optional[int] = None,
) -> List[Polygon]: poly_max_arclen: Optional[float] = None,
) -> List[Polygon]:
if poly_num_points is None: if poly_num_points is None:
poly_num_points = self.poly_num_points poly_num_points = self.poly_num_points
if poly_max_arclen is None: if poly_max_arclen is None:

@ -35,26 +35,27 @@ class Circle(Shape, metaclass=AutoSlots):
return self._radius return self._radius
@radius.setter @radius.setter
def radius(self, val: float): def radius(self, val: float) -> None:
if not is_scalar(val): if not is_scalar(val):
raise PatternError('Radius must be a scalar') raise PatternError('Radius must be a scalar')
if not val >= 0: if not val >= 0:
raise PatternError('Radius must be non-negative') raise PatternError('Radius must be non-negative')
self._radius = val self._radius = val
def __init__(self, def __init__(
radius: float, self,
*, radius: float,
poly_num_points: Optional[int] = DEFAULT_POLY_NUM_POINTS, *,
poly_max_arclen: Optional[float] = None, poly_num_points: Optional[int] = DEFAULT_POLY_NUM_POINTS,
offset: vector2 = (0.0, 0.0), poly_max_arclen: Optional[float] = None,
layer: layer_t = 0, offset: vector2 = (0.0, 0.0),
dose: float = 1.0, layer: layer_t = 0,
repetition: Optional[Repetition] = None, dose: float = 1.0,
annotations: Optional[annotations_t] = None, repetition: Optional[Repetition] = None,
locked: bool = False, annotations: Optional[annotations_t] = None,
raw: bool = False, locked: bool = False,
): raw: bool = False,
) -> None:
LockableImpl.unlock(self) LockableImpl.unlock(self)
self.identifier = () self.identifier = ()
if raw: if raw:
@ -83,10 +84,11 @@ class Circle(Shape, metaclass=AutoSlots):
new.set_locked(self.locked) new.set_locked(self.locked)
return new return new
def to_polygons(self, def to_polygons(
poly_num_points: Optional[int] = None, self,
poly_max_arclen: Optional[float] = None, poly_num_points: Optional[int] = None,
) -> List[Polygon]: poly_max_arclen: Optional[float] = None,
) -> List[Polygon]:
if poly_num_points is None: if poly_num_points is None:
poly_num_points = self.poly_num_points poly_num_points = self.poly_num_points
if poly_max_arclen is None: if poly_max_arclen is None:

@ -41,7 +41,7 @@ class Ellipse(Shape, metaclass=AutoSlots):
return self._radii return self._radii
@radii.setter @radii.setter
def radii(self, val: vector2): def radii(self, val: vector2) -> None:
val = numpy.array(val).flatten() val = numpy.array(val).flatten()
if not val.size == 2: if not val.size == 2:
raise PatternError('Radii must have length 2') raise PatternError('Radii must have length 2')
@ -54,7 +54,7 @@ class Ellipse(Shape, metaclass=AutoSlots):
return self.radii[0] return self.radii[0]
@radius_x.setter @radius_x.setter
def radius_x(self, val: float): def radius_x(self, val: float) -> None:
if not val >= 0: if not val >= 0:
raise PatternError('Radius must be non-negative') raise PatternError('Radius must be non-negative')
self.radii[0] = val self.radii[0] = val
@ -64,7 +64,7 @@ class Ellipse(Shape, metaclass=AutoSlots):
return self.radii[1] return self.radii[1]
@radius_y.setter @radius_y.setter
def radius_y(self, val: float): def radius_y(self, val: float) -> None:
if not val >= 0: if not val >= 0:
raise PatternError('Radius must be non-negative') raise PatternError('Radius must be non-negative')
self.radii[1] = val self.radii[1] = val
@ -82,26 +82,27 @@ class Ellipse(Shape, metaclass=AutoSlots):
return self._rotation return self._rotation
@rotation.setter @rotation.setter
def rotation(self, val: float): def rotation(self, val: float) -> None:
if not is_scalar(val): if not is_scalar(val):
raise PatternError('Rotation must be a scalar') raise PatternError('Rotation must be a scalar')
self._rotation = val % pi self._rotation = val % pi
def __init__(self, def __init__(
radii: vector2, self,
*, radii: vector2,
poly_num_points: Optional[int] = DEFAULT_POLY_NUM_POINTS, *,
poly_max_arclen: Optional[float] = None, poly_num_points: Optional[int] = DEFAULT_POLY_NUM_POINTS,
offset: vector2 = (0.0, 0.0), poly_max_arclen: Optional[float] = None,
rotation: float = 0, offset: vector2 = (0.0, 0.0),
mirrored: Sequence[bool] = (False, False), rotation: float = 0,
layer: layer_t = 0, mirrored: Sequence[bool] = (False, False),
dose: float = 1.0, layer: layer_t = 0,
repetition: Optional[Repetition] = None, dose: float = 1.0,
annotations: Optional[annotations_t] = None, repetition: Optional[Repetition] = None,
locked: bool = False, annotations: Optional[annotations_t] = None,
raw: bool = False, locked: bool = False,
): raw: bool = False,
) -> None:
LockableImpl.unlock(self) LockableImpl.unlock(self)
self.identifier = () self.identifier = ()
if raw: if raw:
@ -134,10 +135,11 @@ class Ellipse(Shape, metaclass=AutoSlots):
new.set_locked(self.locked) new.set_locked(self.locked)
return new return new
def to_polygons(self, def to_polygons(
poly_num_points: Optional[int] = None, self,
poly_max_arclen: Optional[float] = None, poly_num_points: Optional[int] = None,
) -> List[Polygon]: poly_max_arclen: Optional[float] = None,
) -> List[Polygon]:
if poly_num_points is None: if poly_num_points is None:
poly_num_points = self.poly_num_points poly_num_points = self.poly_num_points
if poly_max_arclen is None: if poly_max_arclen is None:

@ -46,7 +46,7 @@ class Path(Shape, metaclass=AutoSlots):
return self._width return self._width
@width.setter @width.setter
def width(self, val: float): def width(self, val: float) -> None:
if not is_scalar(val): if not is_scalar(val):
raise PatternError('Width must be a scalar') raise PatternError('Width must be a scalar')
if not val >= 0: if not val >= 0:
@ -62,7 +62,7 @@ class Path(Shape, metaclass=AutoSlots):
return self._cap return self._cap
@cap.setter @cap.setter
def cap(self, val: PathCap): def cap(self, val: PathCap) -> None:
# TODO: Document that setting cap can change cap_extensions # TODO: Document that setting cap can change cap_extensions
self._cap = PathCap(val) self._cap = PathCap(val)
if self.cap != PathCap.SquareCustom: if self.cap != PathCap.SquareCustom:
@ -83,7 +83,7 @@ class Path(Shape, metaclass=AutoSlots):
return self._cap_extensions return self._cap_extensions
@cap_extensions.setter @cap_extensions.setter
def cap_extensions(self, vals: Optional[numpy.ndarray]): def cap_extensions(self, vals: Optional[numpy.ndarray]) -> None:
custom_caps = (PathCap.SquareCustom,) custom_caps = (PathCap.SquareCustom,)
if self.cap in custom_caps: if self.cap in custom_caps:
if vals is None: if vals is None:
@ -103,7 +103,7 @@ class Path(Shape, metaclass=AutoSlots):
return self._vertices return self._vertices
@vertices.setter @vertices.setter
def vertices(self, val: ArrayLike): def vertices(self, val: ArrayLike) -> None:
val = numpy.array(val, dtype=float) # TODO document that these might not be copied val = numpy.array(val, dtype=float) # TODO document that these might not be copied
if len(val.shape) < 2 or val.shape[1] != 2: if len(val.shape) < 2 or val.shape[1] != 2:
raise PatternError('Vertices must be an Nx2 array') raise PatternError('Vertices must be an Nx2 array')
@ -120,7 +120,7 @@ class Path(Shape, metaclass=AutoSlots):
return self.vertices[:, 0] return self.vertices[:, 0]
@xs.setter @xs.setter
def xs(self, val: ArrayLike): def xs(self, val: ArrayLike) -> None:
val = numpy.array(val, dtype=float).flatten() val = numpy.array(val, dtype=float).flatten()
if val.size != self.vertices.shape[0]: if val.size != self.vertices.shape[0]:
raise PatternError('Wrong number of vertices') raise PatternError('Wrong number of vertices')
@ -135,28 +135,29 @@ class Path(Shape, metaclass=AutoSlots):
return self.vertices[:, 1] return self.vertices[:, 1]
@ys.setter @ys.setter
def ys(self, val: ArrayLike): def ys(self, val: ArrayLike) -> None:
val = numpy.array(val, dtype=float).flatten() val = numpy.array(val, dtype=float).flatten()
if val.size != self.vertices.shape[0]: if val.size != self.vertices.shape[0]:
raise PatternError('Wrong number of vertices') raise PatternError('Wrong number of vertices')
self.vertices[:, 1] = val self.vertices[:, 1] = val
def __init__(self, def __init__(
vertices: ArrayLike, self,
width: float = 0.0, vertices: ArrayLike,
*, width: float = 0.0,
cap: PathCap = PathCap.Flush, *,
cap_extensions: Optional[ArrayLike] = None, cap: PathCap = PathCap.Flush,
offset: vector2 = (0.0, 0.0), cap_extensions: Optional[ArrayLike] = None,
rotation: float = 0, offset: vector2 = (0.0, 0.0),
mirrored: Sequence[bool] = (False, False), rotation: float = 0,
layer: layer_t = 0, mirrored: Sequence[bool] = (False, False),
dose: float = 1.0, layer: layer_t = 0,
repetition: Optional[Repetition] = None, dose: float = 1.0,
annotations: Optional[annotations_t] = None, repetition: Optional[Repetition] = None,
locked: bool = False, annotations: Optional[annotations_t] = None,
raw: bool = False, locked: bool = False,
): raw: bool = False,
) -> None:
LockableImpl.unlock(self) LockableImpl.unlock(self)
self._cap_extensions = None # Since .cap setter might access it self._cap_extensions = None # Since .cap setter might access it
@ -197,16 +198,17 @@ class Path(Shape, metaclass=AutoSlots):
return new return new
@staticmethod @staticmethod
def travel(travel_pairs: Tuple[Tuple[float, float]], def travel(
width: float = 0.0, travel_pairs: Tuple[Tuple[float, float]],
cap: PathCap = PathCap.Flush, width: float = 0.0,
cap_extensions: Optional[Tuple[float, float]] = None, cap: PathCap = PathCap.Flush,
offset: vector2 = (0.0, 0.0), cap_extensions: Optional[Tuple[float, float]] = None,
rotation: float = 0, offset: vector2 = (0.0, 0.0),
mirrored: Sequence[bool] = (False, False), rotation: float = 0,
layer: layer_t = 0, mirrored: Sequence[bool] = (False, False),
dose: float = 1.0, layer: layer_t = 0,
) -> 'Path': dose: float = 1.0,
) -> 'Path':
""" """
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.
@ -243,10 +245,11 @@ class Path(Shape, metaclass=AutoSlots):
offset=offset, rotation=rotation, mirrored=mirrored, offset=offset, rotation=rotation, mirrored=mirrored,
layer=layer, dose=dose) layer=layer, dose=dose)
def to_polygons(self, def to_polygons(
poly_num_points: int = None, self,
poly_max_arclen: float = None, poly_num_points: int = None,
) -> List['Polygon']: poly_max_arclen: float = None,
) -> List['Polygon']:
extensions = self._calculate_cap_extensions() extensions = self._calculate_cap_extensions()
v = remove_colinear_vertices(self.vertices, closed_path=False) v = remove_colinear_vertices(self.vertices, closed_path=False)

@ -34,7 +34,7 @@ class Polygon(Shape, metaclass=AutoSlots):
return self._vertices return self._vertices
@vertices.setter @vertices.setter
def vertices(self, val: ArrayLike): def vertices(self, val: ArrayLike) -> None:
val = numpy.array(val, dtype=float) # TODO document that these might not be copied val = numpy.array(val, dtype=float) # TODO document that these might not be copied
if len(val.shape) < 2 or val.shape[1] != 2: if len(val.shape) < 2 or val.shape[1] != 2:
raise PatternError('Vertices must be an Nx2 array') raise PatternError('Vertices must be an Nx2 array')
@ -51,7 +51,7 @@ class Polygon(Shape, metaclass=AutoSlots):
return self.vertices[:, 0] return self.vertices[:, 0]
@xs.setter @xs.setter
def xs(self, val: ArrayLike): def xs(self, val: ArrayLike) -> None:
val = numpy.array(val, dtype=float).flatten() val = numpy.array(val, dtype=float).flatten()
if val.size != self.vertices.shape[0]: if val.size != self.vertices.shape[0]:
raise PatternError('Wrong number of vertices') raise PatternError('Wrong number of vertices')
@ -66,25 +66,26 @@ class Polygon(Shape, metaclass=AutoSlots):
return self.vertices[:, 1] return self.vertices[:, 1]
@ys.setter @ys.setter
def ys(self, val: ArrayLike): def ys(self, val: ArrayLike) -> None:
val = numpy.array(val, dtype=float).flatten() val = numpy.array(val, dtype=float).flatten()
if val.size != self.vertices.shape[0]: if val.size != self.vertices.shape[0]:
raise PatternError('Wrong number of vertices') raise PatternError('Wrong number of vertices')
self.vertices[:, 1] = val self.vertices[:, 1] = val
def __init__(self, def __init__(
vertices: ArrayLike, self,
*, vertices: ArrayLike,
offset: vector2 = (0.0, 0.0), *,
rotation: float = 0.0, offset: vector2 = (0.0, 0.0),
mirrored: Sequence[bool] = (False, False), rotation: float = 0.0,
layer: layer_t = 0, mirrored: Sequence[bool] = (False, False),
dose: float = 1.0, layer: layer_t = 0,
repetition: Optional[Repetition] = None, dose: float = 1.0,
annotations: Optional[annotations_t] = None, repetition: Optional[Repetition] = None,
locked: bool = False, annotations: Optional[annotations_t] = None,
raw: bool = False, locked: bool = False,
): raw: bool = False,
) -> None:
LockableImpl.unlock(self) LockableImpl.unlock(self)
self.identifier = () self.identifier = ()
if raw: if raw:
@ -115,14 +116,15 @@ class Polygon(Shape, metaclass=AutoSlots):
return new return new
@staticmethod @staticmethod
def square(side_length: float, def square(
*, side_length: float,
rotation: float = 0.0, *,
offset: vector2 = (0.0, 0.0), rotation: float = 0.0,
layer: layer_t = 0, offset: vector2 = (0.0, 0.0),
dose: float = 1.0, layer: layer_t = 0,
repetition: Optional[Repetition] = None, dose: float = 1.0,
) -> 'Polygon': repetition: Optional[Repetition] = None,
) -> 'Polygon':
""" """
Draw a square given side_length, centered on the origin. Draw a square given side_length, centered on the origin.
@ -148,15 +150,16 @@ class Polygon(Shape, metaclass=AutoSlots):
return poly return poly
@staticmethod @staticmethod
def rectangle(lx: float, def rectangle(
ly: float, lx: float,
*, ly: float,
rotation: float = 0, *,
offset: vector2 = (0.0, 0.0), rotation: float = 0,
layer: layer_t = 0, offset: vector2 = (0.0, 0.0),
dose: float = 1.0, layer: layer_t = 0,
repetition: Optional[Repetition] = None, dose: float = 1.0,
) -> 'Polygon': repetition: Optional[Repetition] = None,
) -> 'Polygon':
""" """
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.
@ -182,19 +185,20 @@ class Polygon(Shape, metaclass=AutoSlots):
return poly return poly
@staticmethod @staticmethod
def rect(*, def rect(
xmin: Optional[float] = None, *,
xctr: Optional[float] = None, xmin: Optional[float] = None,
xmax: Optional[float] = None, xctr: Optional[float] = None,
lx: Optional[float] = None, xmax: Optional[float] = None,
ymin: Optional[float] = None, lx: Optional[float] = None,
yctr: Optional[float] = None, ymin: Optional[float] = None,
ymax: Optional[float] = None, yctr: Optional[float] = None,
ly: Optional[float] = None, ymax: Optional[float] = None,
layer: layer_t = 0, ly: Optional[float] = None,
dose: float = 1.0, layer: layer_t = 0,
repetition: Optional[Repetition] = None, dose: float = 1.0,
) -> 'Polygon': repetition: Optional[Repetition] = None,
) -> 'Polygon':
""" """
Draw a rectangle by specifying side/center positions. Draw a rectangle by specifying side/center positions.
@ -282,16 +286,17 @@ class Polygon(Shape, metaclass=AutoSlots):
return poly return poly
@staticmethod @staticmethod
def octagon(*, def octagon(
side_length: Optional[float] = None, *,
inner_radius: Optional[float] = None, side_length: Optional[float] = None,
regular: bool = True, inner_radius: Optional[float] = None,
center: vector2 = (0.0, 0.0), regular: bool = True,
rotation: float = 0.0, center: vector2 = (0.0, 0.0),
layer: layer_t = 0, rotation: float = 0.0,
dose: float = 1.0, layer: layer_t = 0,
repetition: Optional[Repetition] = None, dose: float = 1.0,
) -> 'Polygon': repetition: Optional[Repetition] = None,
) -> 'Polygon':
""" """
Draw an octagon given one of (side length, inradius, circumradius). Draw an octagon given one of (side length, inradius, circumradius).
@ -341,10 +346,11 @@ class Polygon(Shape, metaclass=AutoSlots):
return poly return poly
def to_polygons(self, def to_polygons(
poly_num_points: int = None, # unused self,
poly_max_arclen: float = None, # unused poly_num_points: int = None, # unused
) -> List['Polygon']: poly_max_arclen: float = None, # unused
) -> List['Polygon']:
return [copy.deepcopy(self)] return [copy.deepcopy(self)]
def get_bounds(self) -> numpy.ndarray: def get_bounds(self) -> numpy.ndarray:

@ -47,10 +47,11 @@ class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable
--- Abstract methods --- Abstract methods
''' '''
@abstractmethod @abstractmethod
def to_polygons(self, def to_polygons(
num_vertices: Optional[int] = None, self,
max_arclen: Optional[float] = None, num_vertices: Optional[int] = None,
) -> List['Polygon']: max_arclen: Optional[float] = None,
) -> List['Polygon']:
""" """
Returns a list of polygons which approximate the shape. Returns a list of polygons which approximate the shape.
@ -93,10 +94,11 @@ class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable
''' '''
---- Non-abstract methods ---- Non-abstract methods
''' '''
def manhattanize_fast(self, def manhattanize_fast(
grid_x: ArrayLike, self,
grid_y: ArrayLike, grid_x: ArrayLike,
) -> List['Polygon']: grid_y: ArrayLike,
) -> List['Polygon']:
""" """
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.
@ -200,10 +202,11 @@ class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable
return manhattan_polygons return manhattan_polygons
def manhattanize(self, def manhattanize(
grid_x: ArrayLike, self,
grid_y: ArrayLike, grid_x: ArrayLike,
) -> List['Polygon']: grid_y: ArrayLike,
) -> List['Polygon']:
""" """
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.

@ -35,7 +35,7 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
return self._string return self._string
@string.setter @string.setter
def string(self, val: str): def string(self, val: str) -> None:
self._string = val self._string = val
# Height property # Height property
@ -44,7 +44,7 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
return self._height return self._height
@height.setter @height.setter
def height(self, val: float): def height(self, val: float) -> None:
if not is_scalar(val): if not is_scalar(val):
raise PatternError('Height must be a scalar') raise PatternError('Height must be a scalar')
self._height = val self._height = val
@ -55,26 +55,27 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
return self._mirrored return self._mirrored
@mirrored.setter @mirrored.setter
def mirrored(self, val: Sequence[bool]): def mirrored(self, val: Sequence[bool]) -> None:
if is_scalar(val): if is_scalar(val):
raise PatternError('Mirrored must be a 2-element list of booleans') raise PatternError('Mirrored must be a 2-element list of booleans')
self._mirrored = numpy.array(val, dtype=bool, copy=True) self._mirrored = numpy.array(val, dtype=bool, copy=True)
def __init__(self, def __init__(
string: str, self,
height: float, string: str,
font_path: str, height: float,
*, font_path: str,
offset: vector2 = (0.0, 0.0), *,
rotation: float = 0.0, offset: vector2 = (0.0, 0.0),
mirrored: Tuple[bool, bool] = (False, False), rotation: float = 0.0,
layer: layer_t = 0, mirrored: Tuple[bool, bool] = (False, False),
dose: float = 1.0, layer: layer_t = 0,
repetition: Optional[Repetition] = None, dose: float = 1.0,
annotations: Optional[annotations_t] = None, repetition: Optional[Repetition] = None,
locked: bool = False, annotations: Optional[annotations_t] = None,
raw: bool = False, locked: bool = False,
): raw: bool = False,
) -> None:
LockableImpl.unlock(self) LockableImpl.unlock(self)
self.identifier = () self.identifier = ()
if raw: if raw:
@ -109,10 +110,11 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
new.set_locked(self.locked) new.set_locked(self.locked)
return new return new
def to_polygons(self, def to_polygons(
poly_num_points: Optional[int] = None, # unused self,
poly_max_arclen: Optional[float] = None, # unused poly_num_points: Optional[int] = None, # unused
) -> List[Polygon]: poly_max_arclen: Optional[float] = None, # unused
) -> List[Polygon]:
all_polygons = [] all_polygons = []
total_advance = 0.0 total_advance = 0.0
for char in self.string: for char in self.string:
@ -166,10 +168,11 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
return bounds return bounds
def get_char_as_polygons(font_path: str, def get_char_as_polygons(
char: str, font_path: str,
resolution: float = 48 * 64, char: str,
) -> Tuple[List[List[List[float]]], float]: resolution: float = 48 * 64,
) -> Tuple[List[List[List[float]]], float]:
from freetype import Face # type: ignore from freetype import Face # type: ignore
from matplotlib.path import Path # type: ignore from matplotlib.path import Path # type: ignore

@ -46,19 +46,20 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
identifier: Tuple[Any, ...] identifier: Tuple[Any, ...]
""" Arbitrary identifier, used internally by some `masque` functions. """ """ Arbitrary identifier, used internally by some `masque` functions. """
def __init__(self, def __init__(
pattern: Optional['Pattern'], self,
*, pattern: Optional['Pattern'],
offset: vector2 = (0.0, 0.0), *,
rotation: float = 0.0, offset: vector2 = (0.0, 0.0),
mirrored: Optional[Sequence[bool]] = None, rotation: float = 0.0,
dose: float = 1.0, mirrored: Optional[Sequence[bool]] = None,
scale: float = 1.0, dose: float = 1.0,
repetition: Optional[Repetition] = None, scale: float = 1.0,
annotations: Optional[annotations_t] = None, repetition: Optional[Repetition] = None,
locked: bool = False, annotations: Optional[annotations_t] = None,
identifier: Tuple[Any, ...] = (), locked: bool = False,
) -> None: identifier: Tuple[Any, ...] = (),
) -> None:
""" """
Args: Args:
pattern: Pattern to reference. pattern: Pattern to reference.

Loading…
Cancel
Save