Improve type annotations based on mypy errors
This commit is contained in:
parent
bd4085365f
commit
157df47884
@ -6,7 +6,7 @@ import gdsii.library
|
||||
import gdsii.structure
|
||||
import gdsii.elements
|
||||
|
||||
from typing import List, Any, Dict, Tuple, Callable
|
||||
from typing import List, Any, Dict, Tuple, Callable, Union, Sequence, Iterable, Optional
|
||||
import re
|
||||
import io
|
||||
import copy
|
||||
@ -39,13 +39,13 @@ path_cap_map = {
|
||||
}
|
||||
|
||||
|
||||
def write(patterns: Pattern or List[Pattern],
|
||||
def write(patterns: Union[Pattern, List[Pattern]],
|
||||
stream: io.BufferedIOBase,
|
||||
meters_per_unit: float,
|
||||
logical_units_per_unit: float = 1,
|
||||
library_name: str = 'masque-gdsii-write',
|
||||
modify_originals: bool = False,
|
||||
disambiguate_func: Callable[[List[Pattern]], None] = None):
|
||||
disambiguate_func: Callable[[Iterable[Pattern]], None] = None):
|
||||
"""
|
||||
Write a `Pattern` or list of patterns to a GDSII file, by first calling
|
||||
`.polygonize()` to change the shapes into polygons, and then writing patterns
|
||||
@ -119,8 +119,8 @@ def write(patterns: Pattern or List[Pattern],
|
||||
return
|
||||
|
||||
|
||||
def writefile(patterns: List[Pattern] or Pattern,
|
||||
filename: str or pathlib.Path,
|
||||
def writefile(patterns: Union[List[Pattern], Pattern],
|
||||
filename: Union[str, pathlib.Path],
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
@ -137,7 +137,7 @@ def writefile(patterns: List[Pattern] or Pattern,
|
||||
"""
|
||||
path = pathlib.Path(filename)
|
||||
if path.suffix == '.gz':
|
||||
open_func = gzip.open
|
||||
open_func: Callable = gzip.open
|
||||
else:
|
||||
open_func = open
|
||||
|
||||
@ -185,7 +185,8 @@ def dose2dtype(patterns: List[Pattern],
|
||||
dose_vals = set()
|
||||
for pat_id, pat_dose in sd_table:
|
||||
pat = patterns_by_id[pat_id]
|
||||
[dose_vals.add(shape.dose * pat_dose) for shape in pat.shapes]
|
||||
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)))
|
||||
@ -228,10 +229,10 @@ def dose2dtype(patterns: List[Pattern],
|
||||
return patterns, dose_vals_list
|
||||
|
||||
|
||||
def readfile(filename: str or pathlib.Path,
|
||||
def readfile(filename: Union[str, pathlib.Path],
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> (Dict[str, Pattern], Dict[str, Any]):
|
||||
) -> Tuple[Dict[str, Pattern], Dict[str, Any]]:
|
||||
"""
|
||||
Wrapper for `gdsii.read()` that takes a filename or path instead of a stream.
|
||||
|
||||
@ -244,7 +245,7 @@ def readfile(filename: str or pathlib.Path,
|
||||
"""
|
||||
path = pathlib.Path(filename)
|
||||
if path.suffix == '.gz':
|
||||
open_func = gzip.open
|
||||
open_func: Callable = gzip.open
|
||||
else:
|
||||
open_func = open
|
||||
|
||||
@ -256,7 +257,7 @@ def readfile(filename: str or pathlib.Path,
|
||||
def read(stream: io.BufferedIOBase,
|
||||
use_dtype_as_dose: bool = False,
|
||||
clean_vertices: bool = True,
|
||||
) -> (Dict[str, Pattern], Dict[str, Any]):
|
||||
) -> Tuple[Dict[str, Pattern], Dict[str, Any]]:
|
||||
"""
|
||||
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
|
||||
@ -466,8 +467,8 @@ def _aref_to_gridrep(element: gdsii.elements.ARef) -> GridRepetition:
|
||||
return gridrep
|
||||
|
||||
|
||||
def _subpatterns_to_refs(subpatterns: List[SubPattern or GridRepetition]
|
||||
) -> List[gdsii.elements.ARef or gdsii.elements.SRef]:
|
||||
def _subpatterns_to_refs(subpatterns: List[Union[SubPattern, GridRepetition]]
|
||||
) -> List[Union[gdsii.elements.ARef, gdsii.elements.SRef]]:
|
||||
refs = []
|
||||
for subpat in subpatterns:
|
||||
if subpat.pattern is None:
|
||||
@ -574,7 +575,7 @@ def disambiguate_pattern_names(patterns,
|
||||
# Should never happen since zero-length names are replaced
|
||||
raise PatternError('Zero-length name after sanitize+encode,\n originally "{}"'.format(pat.name))
|
||||
if len(encoded_name) > max_name_length:
|
||||
raise PatternError('Pattern name "{}" length > {} after encode,\n originally "{}"'.format(encoded_name, max_name_length, pat.name))
|
||||
raise PatternError('Pattern name "{!r}" length > {} after encode,\n originally "{}"'.format(encoded_name, max_name_length, pat.name))
|
||||
|
||||
pat.name = encoded_name
|
||||
used_names.append(suffixed_name)
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""
|
||||
SVG file format readers and writers
|
||||
"""
|
||||
|
||||
from typing import Dict, Optional
|
||||
import svgwrite
|
||||
import numpy
|
||||
import warnings
|
||||
@ -56,7 +56,7 @@ def writefile(pattern: Pattern,
|
||||
debug=(not custom_attributes))
|
||||
|
||||
# Get a dict of id(pattern) -> pattern
|
||||
patterns_by_id = {**(pattern.referenced_patterns_by_id()), id(pattern): pattern}
|
||||
patterns_by_id = {**(pattern.referenced_patterns_by_id()), id(pattern): pattern} # type: Dict[int, Optional[Pattern]]
|
||||
|
||||
# Now create a group for each row in sd_table (ie, each pattern + dose combination)
|
||||
# and add in any Boundary and Use elements
|
||||
|
@ -2,7 +2,8 @@
|
||||
Base object for containing a lithography mask.
|
||||
"""
|
||||
|
||||
from typing import List, Callable, Tuple, Dict, Union, Set
|
||||
from typing import List, Callable, Tuple, Dict, Union, Set, Sequence, Optional, Type
|
||||
from typing import MutableMapping, Iterable
|
||||
import copy
|
||||
import itertools
|
||||
import pickle
|
||||
@ -39,7 +40,7 @@ class Pattern:
|
||||
labels: List[Label]
|
||||
""" List of all labels in this Pattern. """
|
||||
|
||||
subpatterns: List[SubPattern or GridRepetition]
|
||||
subpatterns: List[Union[SubPattern, GridRepetition]]
|
||||
""" List of all objects referencing other patterns in this Pattern.
|
||||
Examples are SubPattern (gdsii "instances") or GridRepetition (gdsii "arrays")
|
||||
Multiple objects in this list may reference the same Pattern object
|
||||
@ -54,9 +55,9 @@ class Pattern:
|
||||
|
||||
def __init__(self,
|
||||
name: str = '',
|
||||
shapes: List[Shape] = (),
|
||||
labels: List[Label] = (),
|
||||
subpatterns: List[SubPattern] = (),
|
||||
shapes: Sequence[Shape] = (),
|
||||
labels: Sequence[Label] = (),
|
||||
subpatterns: Sequence[Union[SubPattern, GridRepetition]] = (),
|
||||
locked: bool = False,
|
||||
):
|
||||
"""
|
||||
@ -129,7 +130,7 @@ class Pattern:
|
||||
def subset(self,
|
||||
shapes_func: Callable[[Shape], bool] = None,
|
||||
labels_func: Callable[[Label], bool] = None,
|
||||
subpatterns_func: Callable[[SubPattern], bool] = None,
|
||||
subpatterns_func: Callable[[Union[SubPattern, GridRepetition]], bool] = None,
|
||||
recursive: bool = False,
|
||||
) -> 'Pattern':
|
||||
"""
|
||||
@ -172,9 +173,9 @@ class Pattern:
|
||||
return pat
|
||||
|
||||
def apply(self,
|
||||
func: Callable[['Pattern'], 'Pattern'],
|
||||
memo: Dict[int, 'Pattern'] = None,
|
||||
) -> 'Pattern':
|
||||
func: Callable[[Optional['Pattern']], Optional['Pattern']],
|
||||
memo: Optional[Dict[int, Optional['Pattern']]] = None,
|
||||
) -> Optional['Pattern']:
|
||||
"""
|
||||
Recursively apply func() to this pattern and any pattern it references.
|
||||
func() is expected to take and return a Pattern.
|
||||
@ -217,9 +218,9 @@ class Pattern:
|
||||
def dfs(self,
|
||||
visit_before: visitor_function_t = None,
|
||||
visit_after: visitor_function_t = None,
|
||||
transform: numpy.ndarray or bool or None = False ,
|
||||
memo: Dict = None,
|
||||
hierarchy: Tuple['Pattern'] = (),
|
||||
transform: Union[numpy.ndarray, bool, None] = False,
|
||||
memo: Optional[Dict] = None,
|
||||
hierarchy: Tuple['Pattern', ...] = (),
|
||||
) -> 'Pattern':
|
||||
"""
|
||||
Experimental convenience function.
|
||||
@ -270,7 +271,7 @@ class Pattern:
|
||||
|
||||
pat = self
|
||||
if visit_before is not None:
|
||||
pat = visit_before(pat, hierarchy=hierarchy, memo=memo, transform=transform)
|
||||
pat = visit_before(pat, hierarchy=hierarchy, memo=memo, transform=transform) # type: ignore
|
||||
|
||||
for subpattern in self.subpatterns:
|
||||
if transform is not False:
|
||||
@ -293,12 +294,12 @@ class Pattern:
|
||||
hierarchy=hierarchy + (self,))
|
||||
|
||||
if visit_after is not None:
|
||||
pat = visit_after(pat, hierarchy=hierarchy, memo=memo, transform=transform)
|
||||
pat = visit_after(pat, hierarchy=hierarchy, memo=memo, transform=transform) # type: ignore
|
||||
return pat
|
||||
|
||||
def polygonize(self,
|
||||
poly_num_points: int = None,
|
||||
poly_max_arclen: float = None,
|
||||
poly_num_points: Optional[int] = None,
|
||||
poly_max_arclen: Optional[float] = None,
|
||||
) -> 'Pattern':
|
||||
"""
|
||||
Calls `.to_polygons(...)` on all the shapes in this Pattern and any referenced patterns,
|
||||
@ -349,7 +350,7 @@ class Pattern:
|
||||
def subpatternize(self,
|
||||
recursive: bool = True,
|
||||
norm_value: int = int(1e6),
|
||||
exclude_types: Tuple[Shape] = (Polygon,)
|
||||
exclude_types: Tuple[Type] = (Polygon,)
|
||||
) -> 'Pattern':
|
||||
"""
|
||||
Iterates through this `Pattern` and all referenced `Pattern`s. Within each `Pattern`, it iterates
|
||||
@ -387,7 +388,7 @@ class Pattern:
|
||||
# Create a dict which uses the label tuple from `.normalized_form()` as a key, and which
|
||||
# stores `(function_to_create_normalized_shape, [(index_in_shapes, values), ...])`, where
|
||||
# values are the `(offset, scale, rotation, dose)` values as calculated by `.normalized_form()`
|
||||
shape_table = defaultdict(lambda: [None, list()])
|
||||
shape_table: MutableMapping[Tuple, List] = defaultdict(lambda: [None, list()])
|
||||
for i, shape in enumerate(self.shapes):
|
||||
if not any((isinstance(shape, t) for t in exclude_types)):
|
||||
label, values, func = shape.normalized_form(norm_value)
|
||||
@ -429,9 +430,9 @@ class Pattern:
|
||||
is of the form `[[x0, y0], [x1, y1],...]`.
|
||||
"""
|
||||
pat = self.deepcopy().deepunlock().polygonize().flatten()
|
||||
return [shape.vertices + shape.offset for shape in pat.shapes]
|
||||
return [shape.vertices + shape.offset for shape in pat.shapes] # type: ignore # mypy can't figure out that shapes are all Polygons now
|
||||
|
||||
def referenced_patterns_by_id(self) -> Dict[int, 'Pattern']:
|
||||
def referenced_patterns_by_id(self) -> Dict[int, Optional['Pattern']]:
|
||||
"""
|
||||
Create a dictionary with `{id(pat): pat}` for all Pattern objects referenced by this
|
||||
Pattern (operates recursively on all referenced Patterns as well)
|
||||
@ -447,7 +448,7 @@ class Pattern:
|
||||
ids.update(subpat.pattern.referenced_patterns_by_id())
|
||||
return ids
|
||||
|
||||
def referenced_patterns_by_name(self) -> List[Tuple[str, 'Pattern']]:
|
||||
def referenced_patterns_by_name(self) -> List[Tuple[Optional[str], Optional['Pattern']]]:
|
||||
"""
|
||||
Create a list of `(pat.name, pat)` tuples for all Pattern objects referenced by this
|
||||
Pattern (operates recursively on all referenced Patterns as well).
|
||||
@ -507,7 +508,7 @@ class Pattern:
|
||||
"""
|
||||
subpatterns = copy.deepcopy(self.subpatterns)
|
||||
self.subpatterns = []
|
||||
shape_counts = {}
|
||||
shape_counts: Dict[Tuple, int] = {}
|
||||
for subpat in subpatterns:
|
||||
if subpat.pattern is None:
|
||||
continue
|
||||
@ -839,7 +840,7 @@ class Pattern:
|
||||
pyplot.show()
|
||||
|
||||
@staticmethod
|
||||
def find_toplevel(patterns: List['Pattern']) -> List['Pattern']:
|
||||
def find_toplevel(patterns: Iterable['Pattern']) -> List['Pattern']:
|
||||
"""
|
||||
Given a list of Pattern objects, return those that are not referenced by
|
||||
any other pattern.
|
||||
@ -863,7 +864,7 @@ class Pattern:
|
||||
return memo
|
||||
|
||||
patterns = set(patterns)
|
||||
not_toplevel = set()
|
||||
not_toplevel: Set['Pattern'] = set()
|
||||
for pattern in patterns:
|
||||
not_toplevel |= get_children(pattern, not_toplevel)
|
||||
|
||||
|
@ -12,6 +12,8 @@ from numpy import pi
|
||||
from .error import PatternError, PatternLockedError
|
||||
from .utils import is_scalar, rotation_matrix_2d, vector2
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import Pattern
|
||||
|
||||
|
||||
# TODO need top-level comment about what order rotation/scale/offset/mirror/array are applied
|
||||
@ -51,7 +53,7 @@ class GridRepetition:
|
||||
_scale: float
|
||||
""" Scaling factor applied to individual instances in the grid (not the grid vectors) """
|
||||
|
||||
_mirrored: List[bool]
|
||||
_mirrored: numpy.ndarray # ndarray[bool]
|
||||
""" Whether to mirror individual instances across the x and y axes
|
||||
(Applies to individual instances in the grid, not the grid vectors)
|
||||
"""
|
||||
@ -64,7 +66,7 @@ class GridRepetition:
|
||||
_a_count: int
|
||||
""" Number of instances along the direction specified by the `a_vector` """
|
||||
|
||||
_b_vector: numpy.ndarray or None
|
||||
_b_vector: Optional[numpy.ndarray]
|
||||
""" Vector `[x, y]` specifying a second lattice vector for the grid.
|
||||
Specifies center-to-center spacing between adjacent elements.
|
||||
Can be `None` for a 1D array.
|
||||
@ -80,14 +82,14 @@ class GridRepetition:
|
||||
""" If `True`, disallows changes to the GridRepetition """
|
||||
|
||||
def __init__(self,
|
||||
pattern: 'Pattern',
|
||||
pattern: Optional['Pattern'],
|
||||
a_vector: numpy.ndarray,
|
||||
a_count: int,
|
||||
b_vector: numpy.ndarray = None,
|
||||
b_vector: Optional[numpy.ndarray] = None,
|
||||
b_count: int = 1,
|
||||
offset: vector2 = (0.0, 0.0),
|
||||
rotation: float = 0.0,
|
||||
mirrored: List[bool] = None,
|
||||
mirrored: Optional[Sequence[bool]] = None,
|
||||
dose: float = 1.0,
|
||||
scale: float = 1.0,
|
||||
locked: bool = False):
|
||||
@ -155,7 +157,7 @@ class GridRepetition:
|
||||
locked=self.locked)
|
||||
return new
|
||||
|
||||
def __deepcopy__(self, memo: Dict = None) -> 'GridReptition':
|
||||
def __deepcopy__(self, memo: Dict = None) -> 'GridRepetition':
|
||||
memo = {} if memo is None else memo
|
||||
new = copy.copy(self).unlock()
|
||||
new.pattern = copy.deepcopy(self.pattern, memo)
|
||||
@ -230,11 +232,11 @@ class GridRepetition:
|
||||
|
||||
# Mirrored property
|
||||
@property
|
||||
def mirrored(self) -> List[bool]:
|
||||
def mirrored(self) -> numpy.ndarray: # ndarray[bool]
|
||||
return self._mirrored
|
||||
|
||||
@mirrored.setter
|
||||
def mirrored(self, val: List[bool]):
|
||||
def mirrored(self, val: Sequence[bool]):
|
||||
if is_scalar(val):
|
||||
raise PatternError('Mirrored must be a 2-element list of booleans')
|
||||
self._mirrored = numpy.array(val, dtype=bool, copy=True)
|
||||
|
@ -1,4 +1,4 @@
|
||||
from typing import List, Tuple, Dict
|
||||
from typing import List, Tuple, Dict, Optional, Sequence
|
||||
import copy
|
||||
import math
|
||||
import numpy
|
||||
@ -32,10 +32,10 @@ class Arc(Shape):
|
||||
_width: float
|
||||
""" Width of the arc """
|
||||
|
||||
poly_num_points: int
|
||||
poly_num_points: Optional[int]
|
||||
""" Sets the default number of points for `.polygonize()` """
|
||||
|
||||
poly_max_arclen: float
|
||||
poly_max_arclen: Optional[float]
|
||||
""" Sets the default max segement length for `.polygonize()` """
|
||||
|
||||
# radius properties
|
||||
@ -77,7 +77,7 @@ class Arc(Shape):
|
||||
|
||||
# arc start/stop angle properties
|
||||
@property
|
||||
def angles(self) -> vector2:
|
||||
def angles(self) -> numpy.ndarray: #ndarray[float]
|
||||
"""
|
||||
Return the start and stop angles `[a_start, a_stop]`.
|
||||
Angles are measured from x-axis after rotation
|
||||
@ -150,11 +150,11 @@ class Arc(Shape):
|
||||
radii: vector2,
|
||||
angles: vector2,
|
||||
width: float,
|
||||
poly_num_points: int = DEFAULT_POLY_NUM_POINTS,
|
||||
poly_max_arclen: float = None,
|
||||
poly_num_points: Optional[int] = DEFAULT_POLY_NUM_POINTS,
|
||||
poly_max_arclen: Optional[float] = None,
|
||||
offset: vector2 = (0.0, 0.0),
|
||||
rotation: float = 0,
|
||||
mirrored: Tuple[bool] = (False, False),
|
||||
mirrored: Sequence[bool] = (False, False),
|
||||
layer: layer_t = 0,
|
||||
dose: float = 1.0,
|
||||
locked: bool = False):
|
||||
@ -182,8 +182,8 @@ class Arc(Shape):
|
||||
return new
|
||||
|
||||
def to_polygons(self,
|
||||
poly_num_points: int = None,
|
||||
poly_max_arclen: float = None,
|
||||
poly_num_points: Optional[int] = None,
|
||||
poly_max_arclen: Optional[float] = None,
|
||||
) -> List[Polygon]:
|
||||
if poly_num_points is None:
|
||||
poly_num_points = self.poly_num_points
|
||||
|
@ -1,4 +1,4 @@
|
||||
from typing import List, Dict
|
||||
from typing import List, Dict, Optional
|
||||
import copy
|
||||
import numpy
|
||||
from numpy import pi
|
||||
@ -16,10 +16,10 @@ class Circle(Shape):
|
||||
_radius: float
|
||||
""" Circle radius """
|
||||
|
||||
poly_num_points: int
|
||||
poly_num_points: Optional[int]
|
||||
""" Sets the default number of points for `.polygonize()` """
|
||||
|
||||
poly_max_arclen: float
|
||||
poly_max_arclen: Optional[float]
|
||||
""" Sets the default max segement length for `.polygonize()` """
|
||||
|
||||
# radius property
|
||||
@ -40,8 +40,8 @@ class Circle(Shape):
|
||||
|
||||
def __init__(self,
|
||||
radius: float,
|
||||
poly_num_points: int = DEFAULT_POLY_NUM_POINTS,
|
||||
poly_max_arclen: float = None,
|
||||
poly_num_points: Optional[int] = DEFAULT_POLY_NUM_POINTS,
|
||||
poly_max_arclen: Optional[float] = None,
|
||||
offset: vector2 = (0.0, 0.0),
|
||||
layer: layer_t = 0,
|
||||
dose: float = 1.0,
|
||||
@ -64,8 +64,8 @@ class Circle(Shape):
|
||||
return new
|
||||
|
||||
def to_polygons(self,
|
||||
poly_num_points: int = None,
|
||||
poly_max_arclen: float = None,
|
||||
poly_num_points: Optional[int] = None,
|
||||
poly_max_arclen: Optional[float] = None,
|
||||
) -> List[Polygon]:
|
||||
if poly_num_points is None:
|
||||
poly_num_points = self.poly_num_points
|
||||
|
@ -1,4 +1,4 @@
|
||||
from typing import List, Tuple, Dict
|
||||
from typing import List, Tuple, Dict, Sequence, Optional
|
||||
import copy
|
||||
import math
|
||||
import numpy
|
||||
@ -22,10 +22,10 @@ class Ellipse(Shape):
|
||||
_rotation: float
|
||||
""" Angle from x-axis to first radius (ccw, radians) """
|
||||
|
||||
poly_num_points: int
|
||||
poly_num_points: Optional[int]
|
||||
""" Sets the default number of points for `.polygonize()` """
|
||||
|
||||
poly_max_arclen: float
|
||||
poly_max_arclen: Optional[float]
|
||||
""" Sets the default max segement length for `.polygonize()` """
|
||||
|
||||
# radius properties
|
||||
@ -85,11 +85,11 @@ class Ellipse(Shape):
|
||||
|
||||
def __init__(self,
|
||||
radii: vector2,
|
||||
poly_num_points: int = DEFAULT_POLY_NUM_POINTS,
|
||||
poly_max_arclen: float = None,
|
||||
poly_num_points: Optional[int] = DEFAULT_POLY_NUM_POINTS,
|
||||
poly_max_arclen: Optional[float] = None,
|
||||
offset: vector2 = (0.0, 0.0),
|
||||
rotation: float = 0,
|
||||
mirrored: Tuple[bool] = (False, False),
|
||||
mirrored: Sequence[bool] = (False, False),
|
||||
layer: layer_t = 0,
|
||||
dose: float = 1.0,
|
||||
locked: bool = False):
|
||||
@ -114,8 +114,8 @@ class Ellipse(Shape):
|
||||
return new
|
||||
|
||||
def to_polygons(self,
|
||||
poly_num_points: int = None,
|
||||
poly_max_arclen: float = None,
|
||||
poly_num_points: Optional[int] = None,
|
||||
poly_max_arclen: Optional[float] = None,
|
||||
) -> List[Polygon]:
|
||||
if poly_num_points is None:
|
||||
poly_num_points = self.poly_num_points
|
||||
|
@ -1,4 +1,4 @@
|
||||
from typing import List, Tuple, Dict
|
||||
from typing import List, Tuple, Dict, Optional, Sequence
|
||||
import copy
|
||||
from enum import Enum
|
||||
import numpy
|
||||
@ -28,8 +28,8 @@ class Path(Shape):
|
||||
__slots__ = ('_vertices', '_width', '_cap', '_cap_extensions')
|
||||
_vertices: numpy.ndarray
|
||||
_width: float
|
||||
_cap_extensions: numpy.ndarray or None
|
||||
_cap: PathCap
|
||||
_cap_extensions: Optional[numpy.ndarray]
|
||||
|
||||
Cap = PathCap
|
||||
|
||||
@ -69,7 +69,7 @@ class Path(Shape):
|
||||
|
||||
# cap_extensions property
|
||||
@property
|
||||
def cap_extensions(self) -> numpy.ndarray or None:
|
||||
def cap_extensions(self) -> Optional[numpy.ndarray]:
|
||||
"""
|
||||
Path end-cap extension
|
||||
|
||||
@ -144,11 +144,11 @@ class Path(Shape):
|
||||
cap_extensions: numpy.ndarray = None,
|
||||
offset: vector2 = (0.0, 0.0),
|
||||
rotation: float = 0,
|
||||
mirrored: Tuple[bool] = (False, False),
|
||||
mirrored: Sequence[bool] = (False, False),
|
||||
layer: layer_t = 0,
|
||||
dose: float = 1.0,
|
||||
locked: bool = False,
|
||||
) -> 'Path':
|
||||
):
|
||||
self.unlock()
|
||||
self._cap_extensions = None # Since .cap setter might access it
|
||||
|
||||
@ -182,7 +182,7 @@ class Path(Shape):
|
||||
cap_extensions = None,
|
||||
offset: vector2 = (0.0, 0.0),
|
||||
rotation: float = 0,
|
||||
mirrored: Tuple[bool] = (False, False),
|
||||
mirrored: Sequence[bool] = (False, False),
|
||||
layer: layer_t = 0,
|
||||
dose: float = 1.0,
|
||||
) -> 'Path':
|
||||
|
@ -1,4 +1,4 @@
|
||||
from typing import List, Tuple, Dict
|
||||
from typing import List, Tuple, Dict, Optional, Sequence
|
||||
import copy
|
||||
import numpy
|
||||
from numpy import pi
|
||||
@ -71,7 +71,7 @@ class Polygon(Shape):
|
||||
vertices: numpy.ndarray,
|
||||
offset: vector2 = (0.0, 0.0),
|
||||
rotation: float = 0.0,
|
||||
mirrored: Tuple[bool] = (False, False),
|
||||
mirrored: Sequence[bool] = (False, False),
|
||||
layer: layer_t = 0,
|
||||
dose: float = 1.0,
|
||||
locked: bool = False,
|
||||
@ -86,7 +86,7 @@ class Polygon(Shape):
|
||||
[self.mirror(a) for a, do in enumerate(mirrored) if do]
|
||||
self.locked = locked
|
||||
|
||||
def __deepcopy__(self, memo: Dict = None) -> 'Polygon':
|
||||
def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Polygon':
|
||||
memo = {} if memo is None else memo
|
||||
new = copy.copy(self).unlock()
|
||||
new._offset = self._offset.copy()
|
||||
@ -154,14 +154,14 @@ class Polygon(Shape):
|
||||
return poly
|
||||
|
||||
@staticmethod
|
||||
def rect(xmin: float = None,
|
||||
xctr: float = None,
|
||||
xmax: float = None,
|
||||
lx: float = None,
|
||||
ymin: float = None,
|
||||
yctr: float = None,
|
||||
ymax: float = None,
|
||||
ly: float = None,
|
||||
def rect(xmin: Optional[float] = None,
|
||||
xctr: Optional[float] = None,
|
||||
xmax: Optional[float] = None,
|
||||
lx: Optional[float] = None,
|
||||
ymin: Optional[float] = None,
|
||||
yctr: Optional[float] = None,
|
||||
ymax: Optional[float] = None,
|
||||
ly: Optional[float] = None,
|
||||
layer: layer_t = 0,
|
||||
dose: float = 1.0,
|
||||
) -> 'Polygon':
|
||||
@ -188,11 +188,17 @@ class Polygon(Shape):
|
||||
"""
|
||||
if lx is None:
|
||||
if xctr is None:
|
||||
assert(xmin is not None)
|
||||
assert(xmax is not None)
|
||||
xctr = 0.5 * (xmax + xmin)
|
||||
lx = xmax - xmin
|
||||
elif xmax is None:
|
||||
assert(xmin is not None)
|
||||
assert(xctr is not None)
|
||||
lx = 2 * (xctr - xmin)
|
||||
elif xmin is None:
|
||||
assert(xctr is not None)
|
||||
assert(xmax is not None)
|
||||
lx = 2 * (xmax - xctr)
|
||||
else:
|
||||
raise PatternError('Two of xmin, xctr, xmax, lx must be None!')
|
||||
@ -200,19 +206,29 @@ class Polygon(Shape):
|
||||
if xctr is not None:
|
||||
pass
|
||||
elif xmax is None:
|
||||
assert(xmin is not None)
|
||||
assert(lx is not None)
|
||||
xctr = xmin + 0.5 * lx
|
||||
elif xmin is None:
|
||||
assert(xmax is not None)
|
||||
assert(lx is not None)
|
||||
xctr = xmax - 0.5 * lx
|
||||
else:
|
||||
raise PatternError('Two of xmin, xctr, xmax, lx must be None!')
|
||||
|
||||
if ly is None:
|
||||
if yctr is None:
|
||||
assert(ymin is not None)
|
||||
assert(ymax is not None)
|
||||
yctr = 0.5 * (ymax + ymin)
|
||||
ly = ymax - ymin
|
||||
elif ymax is None:
|
||||
assert(ymin is not None)
|
||||
assert(yctr is not None)
|
||||
ly = 2 * (yctr - ymin)
|
||||
elif ymin is None:
|
||||
assert(yctr is not None)
|
||||
assert(ymax is not None)
|
||||
ly = 2 * (ymax - yctr)
|
||||
else:
|
||||
raise PatternError('Two of ymin, yctr, ymax, ly must be None!')
|
||||
@ -220,8 +236,12 @@ class Polygon(Shape):
|
||||
if yctr is not None:
|
||||
pass
|
||||
elif ymax is None:
|
||||
assert(ymin is not None)
|
||||
assert(ly is not None)
|
||||
yctr = ymin + 0.5 * ly
|
||||
elif ymin is None:
|
||||
assert(ly is not None)
|
||||
assert(ymax is not None)
|
||||
yctr = ymax - 0.5 * ly
|
||||
else:
|
||||
raise PatternError('Two of ymin, yctr, ymax, ly must be None!')
|
||||
|
@ -1,4 +1,4 @@
|
||||
from typing import List, Tuple, Callable
|
||||
from typing import List, Tuple, Callable, TypeVar, Optional, TYPE_CHECKING
|
||||
from abc import ABCMeta, abstractmethod
|
||||
import copy
|
||||
import numpy
|
||||
@ -6,6 +6,8 @@ import numpy
|
||||
from ..error import PatternError, PatternLockedError
|
||||
from ..utils import is_scalar, rotation_matrix_2d, vector2, layer_t
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import Polygon
|
||||
|
||||
|
||||
# Type definitions
|
||||
@ -18,6 +20,9 @@ normalized_shape_tuple = Tuple[Tuple,
|
||||
DEFAULT_POLY_NUM_POINTS = 24
|
||||
|
||||
|
||||
T = TypeVar('T', bound='Shape')
|
||||
|
||||
|
||||
class Shape(metaclass=ABCMeta):
|
||||
"""
|
||||
Abstract class specifying functions common to all shapes.
|
||||
@ -53,7 +58,10 @@ class Shape(metaclass=ABCMeta):
|
||||
|
||||
# --- Abstract methods
|
||||
@abstractmethod
|
||||
def to_polygons(self, num_vertices: int, max_arclen: float) -> List['Polygon']:
|
||||
def to_polygons(self,
|
||||
num_vertices: Optional[int] = None,
|
||||
max_arclen: Optional[float] = None,
|
||||
) -> List['Polygon']:
|
||||
"""
|
||||
Returns a list of polygons which approximate the shape.
|
||||
|
||||
@ -77,7 +85,7 @@ class Shape(metaclass=ABCMeta):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def rotate(self, theta: float) -> 'Shape':
|
||||
def rotate(self: T, theta: float) -> T:
|
||||
"""
|
||||
Rotate the shape around its origin (0, 0), ignoring its offset.
|
||||
|
||||
@ -90,7 +98,7 @@ class Shape(metaclass=ABCMeta):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def mirror(self, axis: int) -> 'Shape':
|
||||
def mirror(self: T, axis: int) -> T:
|
||||
"""
|
||||
Mirror the shape across an axis.
|
||||
|
||||
@ -104,7 +112,7 @@ class Shape(metaclass=ABCMeta):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def scale_by(self, c: float) -> 'Shape':
|
||||
def scale_by(self: T, c: float) -> T:
|
||||
"""
|
||||
Scale the shape's size (eg. radius, for a circle) by a constant factor.
|
||||
|
||||
@ -117,7 +125,7 @@ class Shape(metaclass=ABCMeta):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def normalized_form(self, 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
|
||||
information separated out from the remaining values.
|
||||
@ -187,7 +195,7 @@ class Shape(metaclass=ABCMeta):
|
||||
self._dose = val
|
||||
|
||||
# ---- Non-abstract methods
|
||||
def copy(self) -> 'Shape':
|
||||
def copy(self: T) -> T:
|
||||
"""
|
||||
Returns a deep copy of the shape.
|
||||
|
||||
@ -196,7 +204,7 @@ class Shape(metaclass=ABCMeta):
|
||||
"""
|
||||
return copy.deepcopy(self)
|
||||
|
||||
def translate(self, offset: vector2) -> 'Shape':
|
||||
def translate(self: T, offset: vector2) -> T:
|
||||
"""
|
||||
Translate the shape by the given offset
|
||||
|
||||
@ -209,7 +217,7 @@ class Shape(metaclass=ABCMeta):
|
||||
self.offset += offset
|
||||
return self
|
||||
|
||||
def rotate_around(self, pivot: vector2, rotation: float) -> 'Shape':
|
||||
def rotate_around(self: T, pivot: vector2, rotation: float) -> T:
|
||||
"""
|
||||
Rotate the shape around a point.
|
||||
|
||||
@ -428,7 +436,7 @@ class Shape(metaclass=ABCMeta):
|
||||
|
||||
return manhattan_polygons
|
||||
|
||||
def lock(self) -> 'Shape':
|
||||
def lock(self: T) -> T:
|
||||
"""
|
||||
Lock the Shape, disallowing further changes
|
||||
|
||||
@ -438,7 +446,7 @@ class Shape(metaclass=ABCMeta):
|
||||
object.__setattr__(self, 'locked', True)
|
||||
return self
|
||||
|
||||
def unlock(self) -> 'Shape':
|
||||
def unlock(self: T) -> T:
|
||||
"""
|
||||
Unlock the Shape
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
from typing import List, Tuple, Dict
|
||||
from typing import List, Tuple, Dict, Sequence, Optional, MutableSequence
|
||||
import copy
|
||||
import numpy
|
||||
from numpy import pi, inf
|
||||
@ -21,7 +21,7 @@ class Text(Shape):
|
||||
_string: str
|
||||
_height: float
|
||||
_rotation: float
|
||||
_mirrored: List[str]
|
||||
_mirrored: numpy.ndarray #ndarray[bool]
|
||||
font_path: str
|
||||
|
||||
# vertices property
|
||||
@ -57,11 +57,11 @@ class Text(Shape):
|
||||
|
||||
# Mirrored property
|
||||
@property
|
||||
def mirrored(self) -> List[bool]:
|
||||
def mirrored(self) -> numpy.ndarray: #ndarray[bool]
|
||||
return self._mirrored
|
||||
|
||||
@mirrored.setter
|
||||
def mirrored(self, val: List[bool]):
|
||||
def mirrored(self, val: Sequence[bool]):
|
||||
if is_scalar(val):
|
||||
raise PatternError('Mirrored must be a 2-element list of booleans')
|
||||
self._mirrored = numpy.ndarray(val, dtype=bool, copy=True)
|
||||
@ -72,7 +72,7 @@ class Text(Shape):
|
||||
font_path: str,
|
||||
offset: vector2 = (0.0, 0.0),
|
||||
rotation: float = 0.0,
|
||||
mirrored: Tuple[bool] = (False, False),
|
||||
mirrored: Tuple[bool, bool] = (False, False),
|
||||
layer: layer_t = 0,
|
||||
dose: float = 1.0,
|
||||
locked: bool = False,
|
||||
@ -98,11 +98,11 @@ class Text(Shape):
|
||||
return new
|
||||
|
||||
def to_polygons(self,
|
||||
poly_num_points: int = None, # unused
|
||||
poly_max_arclen: float = None, # unused
|
||||
poly_num_points: Optional[int] = None, # unused
|
||||
poly_max_arclen: Optional[float] = None, # unused
|
||||
) -> List[Polygon]:
|
||||
all_polygons = []
|
||||
total_advance = 0
|
||||
total_advance = 0.0
|
||||
for char in self.string:
|
||||
raw_polys, advance = get_char_as_polygons(self.font_path, char)
|
||||
|
||||
@ -198,7 +198,7 @@ def get_char_as_polygons(font_path: str,
|
||||
tags = outline.tags[start:end + 1]
|
||||
tags.append(tags[0])
|
||||
|
||||
segments = []
|
||||
segments: List[List[List[float]]] = []
|
||||
for j, point in enumerate(points):
|
||||
# If we already have a segment, add this point to it
|
||||
if j > 0:
|
||||
|
@ -12,6 +12,8 @@ from numpy import pi
|
||||
from .error import PatternError, PatternLockedError
|
||||
from .utils import is_scalar, rotation_matrix_2d, vector2
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import Pattern
|
||||
|
||||
|
||||
class SubPattern:
|
||||
@ -43,7 +45,7 @@ class SubPattern:
|
||||
_scale: float
|
||||
""" scale factor for the instance """
|
||||
|
||||
_mirrored: List[bool]
|
||||
_mirrored: numpy.ndarray # ndarray[bool]
|
||||
""" Whether to mirror the instanc across the x and/or y axes. """
|
||||
|
||||
identifier: Tuple
|
||||
@ -58,11 +60,11 @@ class SubPattern:
|
||||
pattern: Optional['Pattern'],
|
||||
offset: vector2 = (0.0, 0.0),
|
||||
rotation: float = 0.0,
|
||||
mirrored: List[bool] = None,
|
||||
mirrored: Optional[Sequence[bool]] = None,
|
||||
dose: float = 1.0,
|
||||
scale: float = 1.0,
|
||||
locked: bool = False):
|
||||
self.unlock()
|
||||
object.__setattr__(self, 'locked', False)
|
||||
self.identifier = ()
|
||||
self.pattern = pattern
|
||||
self.offset = offset
|
||||
@ -161,11 +163,11 @@ class SubPattern:
|
||||
|
||||
# Mirrored property
|
||||
@property
|
||||
def mirrored(self) -> List[bool]:
|
||||
def mirrored(self) -> numpy.ndarray: # ndarray[bool]
|
||||
return self._mirrored
|
||||
|
||||
@mirrored.setter
|
||||
def mirrored(self, val: List[bool]):
|
||||
def mirrored(self, val: Sequence[bool]):
|
||||
if is_scalar(val):
|
||||
raise PatternError('Mirrored must be a 2-element list of booleans')
|
||||
self._mirrored = numpy.array(val, dtype=bool, copy=True)
|
||||
|
@ -2,12 +2,12 @@
|
||||
Various helper functions
|
||||
"""
|
||||
|
||||
from typing import Any, Union, Tuple
|
||||
from typing import Any, Union, Tuple, Sequence
|
||||
|
||||
import numpy
|
||||
|
||||
# Type definitions
|
||||
vector2 = Union[numpy.ndarray, Tuple[float, float]]
|
||||
vector2 = Union[numpy.ndarray, Tuple[float, float], Sequence[float]]
|
||||
layer_t = Union[int, Tuple[int, int]]
|
||||
|
||||
|
||||
@ -68,7 +68,7 @@ def rotation_matrix_2d(theta: float) -> numpy.ndarray:
|
||||
[numpy.sin(theta), +numpy.cos(theta)]])
|
||||
|
||||
|
||||
def normalize_mirror(mirrored: Tuple[bool, bool]) -> Tuple[bool, float]:
|
||||
def normalize_mirror(mirrored: Sequence[bool]) -> Tuple[bool, float]:
|
||||
"""
|
||||
Converts 0-2 mirror operations `(mirror_across_x_axis, mirror_across_y_axis)`
|
||||
into 0-1 mirror operations and a rotation
|
||||
|
Loading…
Reference in New Issue
Block a user