Use ArrayLike and NDArray wherever possible. Some type fixes and some related corner cases
This commit is contained in:
parent
89f327ba37
commit
a4fe3d9e2e
@ -6,14 +6,14 @@ import traceback
|
||||
import logging
|
||||
from collections import Counter
|
||||
|
||||
import numpy # type: ignore
|
||||
import numpy
|
||||
from numpy import pi
|
||||
from numpy.typing import ArrayLike
|
||||
from numpy.typing import ArrayLike, NDArray
|
||||
|
||||
from ..pattern import Pattern
|
||||
from ..subpattern import SubPattern
|
||||
from ..traits import PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable
|
||||
from ..utils import AutoSlots, rotation_matrix_2d, vector2
|
||||
from ..utils import AutoSlots, rotation_matrix_2d
|
||||
from ..error import DeviceError
|
||||
|
||||
|
||||
@ -48,7 +48,8 @@ class Port(PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable, met
|
||||
ptype: str
|
||||
""" Port types must match to be plugged together if both are non-zero """
|
||||
|
||||
def __init__(self,
|
||||
def __init__(
|
||||
self,
|
||||
offset: ArrayLike,
|
||||
rotation: Optional[float],
|
||||
ptype: str = 'unk',
|
||||
@ -63,7 +64,7 @@ class Port(PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable, met
|
||||
return self._rotation
|
||||
|
||||
@rotation.setter
|
||||
def rotation(self, val: float):
|
||||
def rotation(self, val: float) -> None:
|
||||
if val is None:
|
||||
self._rotation = None
|
||||
else:
|
||||
@ -207,6 +208,21 @@ class Device(Copyable, Mirrorable):
|
||||
def __getitem__(self, key: Union[List[str], Tuple[str], KeysView[str], ValuesView[str]]) -> Dict[str, Port]:
|
||||
pass
|
||||
|
||||
#=======
|
||||
## from typing import overload
|
||||
## from _collections_abc import dict_keys, dict_values
|
||||
##
|
||||
## @overload
|
||||
## def __getitem__(self, key: str) -> Port:
|
||||
## pass
|
||||
##
|
||||
## @overload
|
||||
## def __getitem__(self, key: Union[List[str], Tuple[str], dict_keys[str, str], dict_values[str, str]]) -> Dict[str, Port]:
|
||||
### def __getitem__(self, key: Iterable[str]) -> Dict[str, Port]:
|
||||
## pass
|
||||
#
|
||||
## def __getitem__(self, key: Union[str, Iterable[str]]) -> Union[Port, Dict[str, Port]]:
|
||||
# def __getitem__(self, key: Union[str, Iterable[str]]) -> Any:
|
||||
def __getitem__(self, key: Union[str, Iterable[str]]) -> Union[Port, Dict[str, Port]]:
|
||||
"""
|
||||
For convenience, ports can be read out using square brackets:
|
||||
@ -504,9 +520,9 @@ class Device(Copyable, Mirrorable):
|
||||
self: D,
|
||||
other: O,
|
||||
*,
|
||||
offset: vector2 = (0, 0),
|
||||
offset: ArrayLike = (0, 0),
|
||||
rotation: float = 0,
|
||||
pivot: vector2 = (0, 0),
|
||||
pivot: ArrayLike = (0, 0),
|
||||
mirrored: Tuple[bool, bool] = (False, False),
|
||||
port_map: Optional[Dict[str, Optional[str]]] = None,
|
||||
skip_port_check: bool = False,
|
||||
@ -585,7 +601,7 @@ class Device(Copyable, Mirrorable):
|
||||
*,
|
||||
mirrored: Tuple[bool, bool] = (False, False),
|
||||
set_rotation: Optional[bool] = None,
|
||||
) -> Tuple[numpy.ndarray, float, numpy.ndarray]:
|
||||
) -> Tuple[NDArray[numpy.float64], float, NDArray[numpy.float64]]:
|
||||
"""
|
||||
Given a device `other` and a mapping `map_in` specifying port connections,
|
||||
find the transform which will correctly align the specified ports.
|
||||
@ -668,7 +684,7 @@ class Device(Copyable, Mirrorable):
|
||||
|
||||
return translations[0], rotations[0], o_offsets[0]
|
||||
|
||||
def translate(self: D, offset: vector2) -> D:
|
||||
def translate(self: D, offset: ArrayLike) -> D:
|
||||
"""
|
||||
Translate the pattern and all ports.
|
||||
|
||||
@ -683,7 +699,7 @@ class Device(Copyable, Mirrorable):
|
||||
port.translate(offset)
|
||||
return self
|
||||
|
||||
def rotate_around(self: D, pivot: vector2, angle: float) -> D:
|
||||
def rotate_around(self: D, pivot: ArrayLike, angle: float) -> D:
|
||||
"""
|
||||
Translate the pattern and all ports.
|
||||
|
||||
@ -753,10 +769,10 @@ class Device(Copyable, Mirrorable):
|
||||
|
||||
|
||||
def rotate_offsets_around(
|
||||
offsets: ArrayLike,
|
||||
pivot: ArrayLike,
|
||||
offsets: NDArray[numpy.float64],
|
||||
pivot: NDArray[numpy.float64],
|
||||
angle: float,
|
||||
) -> numpy.ndarray:
|
||||
) -> NDArray[numpy.float64]:
|
||||
offsets -= pivot
|
||||
offsets[:] = (rotation_matrix_2d(angle) @ offsets.T).T
|
||||
offsets += pivot
|
||||
|
@ -1,11 +1,12 @@
|
||||
from typing import Dict, Tuple, List, Optional, Union, Any, cast, Sequence
|
||||
from pprint import pformat
|
||||
|
||||
import numpy # type: ignore
|
||||
import numpy
|
||||
from numpy import pi
|
||||
from numpy.typing import ArrayLike
|
||||
|
||||
from .devices import Port
|
||||
from ..utils import rotation_matrix_2d, vector2
|
||||
from ..utils import rotation_matrix_2d
|
||||
from ..error import BuildError
|
||||
|
||||
|
||||
@ -13,9 +14,9 @@ def ell(
|
||||
ports: Dict[str, Port],
|
||||
ccw: Optional[bool],
|
||||
bound_type: str,
|
||||
bound: Union[float, vector2],
|
||||
bound: Union[float, ArrayLike],
|
||||
*,
|
||||
spacing: Optional[Union[float, numpy.ndarray]] = None,
|
||||
spacing: Optional[Union[float, ArrayLike]] = None,
|
||||
set_rotation: Optional[float] = None,
|
||||
) -> Dict[str, float]:
|
||||
"""
|
||||
@ -96,8 +97,11 @@ def ell(
|
||||
rotations[~has_rotation] = rotations[has_rotation][0]
|
||||
|
||||
if not numpy.allclose(rotations[0], rotations):
|
||||
port_rotations = {k: numpy.rad2deg(p.rotation) if p.rotation is not None else None
|
||||
for k, p in ports.items()}
|
||||
|
||||
raise BuildError('Asked to find aggregation for ports that face in different directions:\n'
|
||||
+ pformat({k: numpy.rad2deg(p.rotation) for k, p in ports.items()}))
|
||||
+ pformat(port_rotations))
|
||||
else:
|
||||
if set_rotation is not None:
|
||||
raise BuildError('set_rotation must be specified if no ports have rotations!')
|
||||
|
@ -30,7 +30,8 @@ import logging
|
||||
import pathlib
|
||||
import gzip
|
||||
|
||||
import numpy # type: ignore
|
||||
import numpy
|
||||
from numpy.typing import NDArray
|
||||
import klamath
|
||||
from klamath import records
|
||||
|
||||
@ -369,10 +370,12 @@ def _subpatterns_to_refs(subpatterns: List[SubPattern]) -> List[klamath.library.
|
||||
properties = _annotations_to_properties(subpat.annotations, 512)
|
||||
|
||||
if isinstance(rep, Grid):
|
||||
xy = numpy.array(subpat.offset) + [
|
||||
b_vector = rep.b_vector if rep.b_vector is not None else numpy.zeros(2)
|
||||
b_count = rep.b_count if rep.b_count is not None else 1
|
||||
xy: NDArray[numpy.float64] = numpy.array(subpat.offset) + [
|
||||
[0, 0],
|
||||
rep.a_vector * rep.a_count,
|
||||
rep.b_vector * rep.b_count,
|
||||
b_vector * b_count,
|
||||
]
|
||||
aref = klamath.library.Reference(struct_name=encoded_name,
|
||||
xy=numpy.round(xy).astype(int),
|
||||
|
@ -198,8 +198,7 @@ def writefile(
|
||||
open_func = open
|
||||
|
||||
with io.BufferedWriter(open_func(path, mode='wb')) as stream:
|
||||
results = write(patterns, stream, *args, **kwargs)
|
||||
return results
|
||||
write(patterns, stream, *args, **kwargs)
|
||||
|
||||
|
||||
def readfile(
|
||||
@ -491,10 +490,14 @@ def _placement_to_subpat(placement: fatrec.Placement, lib: fatamorgana.OasisLayo
|
||||
pname = placement.get_name()
|
||||
name = pname if isinstance(pname, int) else pname.string
|
||||
annotations = properties_to_annotations(placement.properties, lib.propnames, lib.propstrings)
|
||||
if placement.angle is None:
|
||||
rotation = 0
|
||||
else:
|
||||
rotation = numpy.deg2rad(float(placement.angle))
|
||||
subpat = SubPattern(offset=xy,
|
||||
pattern=None,
|
||||
mirrored=(placement.flip, False),
|
||||
rotation=numpy.deg2rad(placement.angle),
|
||||
rotation=rotation,
|
||||
scale=float(mag),
|
||||
identifier=(name,),
|
||||
repetition=repetition_fata2masq(placement.repetition),
|
||||
|
@ -28,11 +28,12 @@ import logging
|
||||
import pathlib
|
||||
import gzip
|
||||
|
||||
import numpy # type: ignore
|
||||
import numpy
|
||||
from numpy.typing import ArrayLike, NDArray
|
||||
# python-gdsii
|
||||
import gdsii.library
|
||||
import gdsii.structure
|
||||
import gdsii.elements
|
||||
import gdsii.library #type: ignore
|
||||
import gdsii.structure #type: ignore
|
||||
import gdsii.elements #type: ignore
|
||||
|
||||
from .utils import clean_pattern_vertices, is_gzipped
|
||||
from .. import Pattern, SubPattern, PatternError, Label, Shape
|
||||
@ -182,8 +183,7 @@ def writefile(
|
||||
open_func = open
|
||||
|
||||
with io.BufferedWriter(open_func(path, mode='wb')) as stream:
|
||||
results = write(patterns, stream, *args, **kwargs)
|
||||
return results
|
||||
write(patterns, stream, *args, **kwargs)
|
||||
|
||||
|
||||
def readfile(
|
||||
@ -402,10 +402,12 @@ def _subpatterns_to_refs(
|
||||
new_refs: List[Union[gdsii.elements.SRef, gdsii.elements.ARef]]
|
||||
ref: Union[gdsii.elements.SRef, gdsii.elements.ARef]
|
||||
if isinstance(rep, Grid):
|
||||
xy = numpy.array(subpat.offset) + [
|
||||
b_vector = rep.b_vector if rep.b_vector is not None else numpy.zeros(2)
|
||||
b_count = rep.b_count if rep.b_count is not None else 1
|
||||
xy: NDArray[numpy.float64] = numpy.array(subpat.offset) + [
|
||||
[0, 0],
|
||||
rep.a_vector * rep.a_count,
|
||||
rep.b_vector * rep.b_count,
|
||||
b_vector * b_count,
|
||||
]
|
||||
ref = gdsii.elements.ARef(struct_name=encoded_name,
|
||||
xy=numpy.round(xy).astype(int),
|
||||
|
@ -4,7 +4,8 @@ SVG file format readers and writers
|
||||
from typing import Dict, Optional
|
||||
import warnings
|
||||
|
||||
import numpy # type: ignore
|
||||
import numpy
|
||||
from numpy.typing import ArrayLike
|
||||
import svgwrite # type: ignore
|
||||
|
||||
from .utils import mangle_name
|
||||
@ -141,7 +142,7 @@ def writefile_inverted(pattern: Pattern, filename: str):
|
||||
svg.save()
|
||||
|
||||
|
||||
def poly2path(vertices: numpy.ndarray) -> str:
|
||||
def poly2path(vertices: ArrayLike) -> str:
|
||||
"""
|
||||
Create an SVG path string from an Nx2 list of vertices.
|
||||
|
||||
@ -151,8 +152,9 @@ def poly2path(vertices: numpy.ndarray) -> str:
|
||||
Returns:
|
||||
SVG path-string.
|
||||
"""
|
||||
commands = 'M{:g},{:g} '.format(vertices[0][0], vertices[0][1])
|
||||
for vertex in vertices[1:]:
|
||||
verts = numpy.array(vertices, copy=False)
|
||||
commands = 'M{:g},{:g} '.format(verts[0][0], verts[0][1])
|
||||
for vertex in verts[1:]:
|
||||
commands += 'L{:g},{:g}'.format(vertex[0], vertex[1])
|
||||
commands += ' Z '
|
||||
return commands
|
||||
|
@ -1,9 +1,11 @@
|
||||
from typing import Tuple, Dict, Optional, TypeVar
|
||||
import copy
|
||||
import numpy # type: ignore
|
||||
|
||||
import numpy
|
||||
from numpy.typing import ArrayLike, NDArray
|
||||
|
||||
from .repetition import Repetition
|
||||
from .utils import vector2, rotation_matrix_2d, layer_t, AutoSlots, annotations_t
|
||||
from .utils import rotation_matrix_2d, layer_t, AutoSlots, annotations_t
|
||||
from .traits import PositionableImpl, LayerableImpl, Copyable, Pivotable, LockableImpl, RepeatableImpl
|
||||
from .traits import AnnotatableImpl
|
||||
|
||||
@ -43,7 +45,7 @@ class Label(PositionableImpl, LayerableImpl, LockableImpl, RepeatableImpl, Annot
|
||||
self,
|
||||
string: str,
|
||||
*,
|
||||
offset: vector2 = (0.0, 0.0),
|
||||
offset: ArrayLike = (0.0, 0.0),
|
||||
layer: layer_t = 0,
|
||||
repetition: Optional[Repetition] = None,
|
||||
annotations: Optional[annotations_t] = None,
|
||||
@ -74,7 +76,7 @@ class Label(PositionableImpl, LayerableImpl, LockableImpl, RepeatableImpl, Annot
|
||||
new.set_locked(self.locked)
|
||||
return new
|
||||
|
||||
def rotate_around(self: L, pivot: vector2, rotation: float) -> L:
|
||||
def rotate_around(self: L, pivot: ArrayLike, rotation: float) -> L:
|
||||
"""
|
||||
Rotate the label around a point.
|
||||
|
||||
@ -91,7 +93,7 @@ class Label(PositionableImpl, LayerableImpl, LockableImpl, RepeatableImpl, Annot
|
||||
self.translate(+pivot)
|
||||
return self
|
||||
|
||||
def get_bounds(self) -> numpy.ndarray:
|
||||
def get_bounds(self) -> NDArray[numpy.float64]:
|
||||
"""
|
||||
Return the bounds of the label.
|
||||
|
||||
|
@ -9,21 +9,21 @@ import pickle
|
||||
from itertools import chain
|
||||
from collections import defaultdict
|
||||
|
||||
import numpy # type: ignore
|
||||
import numpy
|
||||
from numpy import inf
|
||||
from numpy.typing import ArrayLike
|
||||
from numpy.typing import NDArray, ArrayLike
|
||||
# .visualize imports matplotlib and matplotlib.collections
|
||||
|
||||
from .subpattern import SubPattern
|
||||
from .shapes import Shape, Polygon
|
||||
from .label import Label
|
||||
from .utils import rotation_matrix_2d, vector2, normalize_mirror, AutoSlots, annotations_t
|
||||
from .utils import rotation_matrix_2d, normalize_mirror, AutoSlots, annotations_t
|
||||
from .error import PatternError, PatternLockedError
|
||||
from .traits import LockableImpl, AnnotatableImpl, Scalable, Mirrorable
|
||||
from .traits import Rotatable, Positionable
|
||||
|
||||
|
||||
visitor_function_t = Callable[['Pattern', Tuple['Pattern'], Dict, numpy.ndarray], 'Pattern']
|
||||
visitor_function_t = Callable[['Pattern', Tuple['Pattern'], Dict, NDArray[numpy.float64]], 'Pattern']
|
||||
|
||||
|
||||
P = TypeVar('P', bound='Pattern')
|
||||
@ -236,7 +236,7 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
self: P,
|
||||
visit_before: visitor_function_t = None,
|
||||
visit_after: visitor_function_t = None,
|
||||
transform: Union[numpy.ndarray, bool, None] = False,
|
||||
transform: Union[ArrayLike, bool, None] = False,
|
||||
memo: Optional[Dict] = None,
|
||||
hierarchy: Tuple[P, ...] = (),
|
||||
) -> P:
|
||||
@ -283,6 +283,8 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
|
||||
if transform is None or transform is True:
|
||||
transform = numpy.zeros(4)
|
||||
elif transform is not False:
|
||||
transform = numpy.array(transform)
|
||||
|
||||
if self in hierarchy:
|
||||
raise PatternError('.dfs() called on pattern with circular reference')
|
||||
@ -441,7 +443,7 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
|
||||
return self
|
||||
|
||||
def as_polygons(self) -> List[numpy.ndarray]:
|
||||
def as_polygons(self) -> List[NDArray[numpy.float64]]:
|
||||
"""
|
||||
Represents the pattern as a list of polygons.
|
||||
|
||||
@ -510,6 +512,7 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
List of `(pat.name, pat)` tuples for all referenced Pattern objects
|
||||
"""
|
||||
pats_by_id = self.referenced_patterns_by_id(**kwargs)
|
||||
pat_list: List[Tuple[Optional[str], Optional['Pattern']]]
|
||||
pat_list = [(p.name if p is not None else None, p) for p in pats_by_id.values()]
|
||||
return pat_list
|
||||
|
||||
@ -539,7 +542,7 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
ids.update(pat.subpatterns_by_id(include_none=include_none))
|
||||
return dict(ids)
|
||||
|
||||
def get_bounds(self) -> Union[numpy.ndarray, None]:
|
||||
def get_bounds(self) -> Union[NDArray[numpy.float64], None]:
|
||||
"""
|
||||
Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the
|
||||
extent of the Pattern's contents in each dimension.
|
||||
@ -553,6 +556,7 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
|
||||
min_bounds = numpy.array((+inf, +inf))
|
||||
max_bounds = numpy.array((-inf, -inf))
|
||||
|
||||
for entry in chain(self.shapes, self.subpatterns, self.labels):
|
||||
bounds = entry.get_bounds()
|
||||
if bounds is None:
|
||||
@ -655,7 +659,7 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
|
||||
return self
|
||||
|
||||
def translate_elements(self: P, offset: vector2) -> P:
|
||||
def translate_elements(self: P, offset: ArrayLike) -> P:
|
||||
"""
|
||||
Translates all shapes, label, and subpatterns by the given offset.
|
||||
|
||||
@ -702,7 +706,7 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
label.offset *= c
|
||||
return self
|
||||
|
||||
def rotate_around(self: P, pivot: vector2, rotation: float) -> P:
|
||||
def rotate_around(self: P, pivot: ArrayLike, rotation: float) -> P:
|
||||
"""
|
||||
Rotate the Pattern around the a location.
|
||||
|
||||
@ -945,7 +949,7 @@ class Pattern(LockableImpl, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
|
||||
def visualize(
|
||||
self,
|
||||
offset: vector2 = (0., 0.),
|
||||
offset: ArrayLike = (0., 0.),
|
||||
line_color: str = 'k',
|
||||
fill_color: str = 'none',
|
||||
overdraw: bool = False,
|
||||
|
@ -7,11 +7,11 @@ from typing import Union, Dict, Optional, Sequence, Any
|
||||
import copy
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import numpy # type: ignore
|
||||
from numpy.typing import ArrayLike
|
||||
import numpy
|
||||
from numpy.typing import ArrayLike, NDArray
|
||||
|
||||
from .error import PatternError
|
||||
from .utils import rotation_matrix_2d, vector2, AutoSlots
|
||||
from .utils import rotation_matrix_2d, AutoSlots
|
||||
from .traits import LockableImpl, Copyable, Scalable, Rotatable, Mirrorable
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ class Repetition(Copyable, Rotatable, Mirrorable, Scalable, metaclass=ABCMeta):
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def displacements(self) -> numpy.ndarray:
|
||||
def displacements(self) -> NDArray[numpy.float64]:
|
||||
"""
|
||||
An Nx2 ndarray specifying all offsets generated by this repetition
|
||||
"""
|
||||
@ -44,7 +44,7 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
||||
'_a_count',
|
||||
'_b_count')
|
||||
|
||||
_a_vector: numpy.ndarray
|
||||
_a_vector: NDArray[numpy.float64]
|
||||
""" Vector `[x, y]` specifying the first lattice vector of the grid.
|
||||
Specifies center-to-center spacing between adjacent elements.
|
||||
"""
|
||||
@ -52,7 +52,7 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
||||
_a_count: int
|
||||
""" Number of instances along the direction specified by the `a_vector` """
|
||||
|
||||
_b_vector: Optional[numpy.ndarray]
|
||||
_b_vector: Optional[NDArray[numpy.float64]]
|
||||
""" 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.
|
||||
@ -100,8 +100,8 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
||||
raise PatternError(f'Repetition has too-small b_count: {b_count}')
|
||||
|
||||
object.__setattr__(self, 'locked', False)
|
||||
self.a_vector = a_vector
|
||||
self.b_vector = b_vector
|
||||
self.a_vector = a_vector # type: ignore # setter handles type conversion
|
||||
self.b_vector = b_vector # type: ignore # setter handles type conversion
|
||||
self.a_count = a_count
|
||||
self.b_count = b_count
|
||||
self.locked = locked
|
||||
@ -122,11 +122,11 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
||||
|
||||
# a_vector property
|
||||
@property
|
||||
def a_vector(self) -> numpy.ndarray:
|
||||
def a_vector(self) -> NDArray[numpy.float64]:
|
||||
return self._a_vector
|
||||
|
||||
@a_vector.setter
|
||||
def a_vector(self, val: vector2):
|
||||
def a_vector(self, val: ArrayLike) -> None:
|
||||
if not isinstance(val, numpy.ndarray):
|
||||
val = numpy.array(val, dtype=float)
|
||||
|
||||
@ -136,11 +136,11 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
||||
|
||||
# b_vector property
|
||||
@property
|
||||
def b_vector(self) -> numpy.ndarray:
|
||||
def b_vector(self) -> Optional[NDArray[numpy.float64]]:
|
||||
return self._b_vector
|
||||
|
||||
@b_vector.setter
|
||||
def b_vector(self, val: vector2):
|
||||
def b_vector(self, val: ArrayLike) -> None:
|
||||
if not isinstance(val, numpy.ndarray):
|
||||
val = numpy.array(val, dtype=float, copy=True)
|
||||
|
||||
@ -154,7 +154,7 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
||||
return self._a_count
|
||||
|
||||
@a_count.setter
|
||||
def a_count(self, val: int):
|
||||
def a_count(self, val: int) -> None:
|
||||
if val != int(val):
|
||||
raise PatternError('a_count must be convertable to an int!')
|
||||
self._a_count = int(val)
|
||||
@ -165,13 +165,16 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
||||
return self._b_count
|
||||
|
||||
@b_count.setter
|
||||
def b_count(self, val: int):
|
||||
def b_count(self, val: int) -> None:
|
||||
if val != int(val):
|
||||
raise PatternError('b_count must be convertable to an int!')
|
||||
self._b_count = int(val)
|
||||
|
||||
@property
|
||||
def displacements(self) -> numpy.ndarray:
|
||||
def displacements(self) -> NDArray[numpy.float64]:
|
||||
if self.b_vector is None:
|
||||
return numpy.arange(self.a_count)[:, None] * self.a_vector[None, :]
|
||||
|
||||
aa, bb = numpy.meshgrid(numpy.arange(self.a_count), numpy.arange(self.b_count), indexing='ij')
|
||||
return (aa.flatten()[:, None] * self.a_vector[None, :]
|
||||
+ bb.flatten()[:, None] * self.b_vector[None, :]) # noqa
|
||||
@ -207,7 +210,7 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
||||
self.b_vector[1 - axis] *= -1
|
||||
return self
|
||||
|
||||
def get_bounds(self) -> Optional[numpy.ndarray]:
|
||||
def get_bounds(self) -> Optional[NDArray[numpy.float64]]:
|
||||
"""
|
||||
Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the
|
||||
extent of the `Grid` in each dimension.
|
||||
@ -216,7 +219,7 @@ class Grid(LockableImpl, Repetition, metaclass=AutoSlots):
|
||||
`[[x_min, y_min], [x_max, y_max]]` or `None`
|
||||
"""
|
||||
a_extent = self.a_vector * self.a_count
|
||||
b_extent = self.b_vector * self.b_count if self.b_count != 0 else 0
|
||||
b_extent = self.b_vector * self.b_count if (self.b_vector is not None) else 0 # type: Union[NDArray[numpy.float64], float]
|
||||
|
||||
corners = ((0, 0), a_extent, b_extent, a_extent + b_extent)
|
||||
xy_min = numpy.min(corners, axis=0)
|
||||
@ -296,24 +299,26 @@ class Arbitrary(LockableImpl, Repetition, metaclass=AutoSlots):
|
||||
`[[x0, y0], [x1, y1], ...]`
|
||||
"""
|
||||
|
||||
_displacements: numpy.ndarray
|
||||
_displacements: NDArray[numpy.float64]
|
||||
""" List of vectors `[[x0, y0], [x1, y1], ...]` specifying the offsets
|
||||
of the instances.
|
||||
"""
|
||||
|
||||
@property
|
||||
def displacements(self) -> numpy.ndarray:
|
||||
def displacements(self) -> Any: # TODO: mypy#3004 NDArray[numpy.float64]:
|
||||
return self._displacements
|
||||
|
||||
@displacements.setter
|
||||
def displacements(self, val: ArrayLike):
|
||||
val = numpy.array(val, float)
|
||||
val = numpy.sort(val.view([('', val.dtype)] * val.shape[1]), 0).view(val.dtype) # sort rows
|
||||
self._displacements = val
|
||||
def displacements(self, val: ArrayLike) -> None:
|
||||
vala: NDArray[numpy.float64] = numpy.array(vala, dtype=float)
|
||||
vala = numpy.sort(vala.view([('', vala.dtype)] * vala.shape[1]), 0).view(vala.dtype) # sort rows
|
||||
self._displacements = vala
|
||||
|
||||
def __init__(self,
|
||||
def __init__(
|
||||
self,
|
||||
displacements: ArrayLike,
|
||||
locked: bool = False,):
|
||||
locked: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
Args:
|
||||
displacements: List of vectors (Nx2 ndarray) specifying displacements.
|
||||
@ -383,7 +388,7 @@ class Arbitrary(LockableImpl, Repetition, metaclass=AutoSlots):
|
||||
self.displacements[1 - axis] *= -1
|
||||
return self
|
||||
|
||||
def get_bounds(self) -> Optional[numpy.ndarray]:
|
||||
def get_bounds(self) -> Optional[NDArray[numpy.float64]]:
|
||||
"""
|
||||
Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the
|
||||
extent of the `displacements` in each dimension.
|
||||
|
@ -1,14 +1,15 @@
|
||||
from typing import List, Dict, Optional, Sequence
|
||||
from typing import List, Dict, Optional, Sequence, Any
|
||||
import copy
|
||||
import math
|
||||
|
||||
import numpy # type: ignore
|
||||
import numpy
|
||||
from numpy import pi
|
||||
from numpy.typing import NDArray, ArrayLike
|
||||
|
||||
from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS
|
||||
from .. import PatternError
|
||||
from ..repetition import Repetition
|
||||
from ..utils import is_scalar, vector2, layer_t, AutoSlots, annotations_t
|
||||
from ..utils import is_scalar, layer_t, AutoSlots, annotations_t
|
||||
from ..traits import LockableImpl
|
||||
|
||||
|
||||
@ -24,13 +25,13 @@ class Arc(Shape, metaclass=AutoSlots):
|
||||
__slots__ = ('_radii', '_angles', '_width', '_rotation',
|
||||
'poly_num_points', 'poly_max_arclen')
|
||||
|
||||
_radii: numpy.ndarray
|
||||
_radii: NDArray[numpy.float64]
|
||||
""" Two radii for defining an ellipse """
|
||||
|
||||
_rotation: float
|
||||
""" Rotation (ccw, radians) from the x axis to the first radius """
|
||||
|
||||
_angles: numpy.ndarray
|
||||
_angles: NDArray[numpy.float64]
|
||||
""" Start and stop angles (ccw, radians) for choosing an arc from the ellipse, measured from the first radius """
|
||||
|
||||
_width: float
|
||||
@ -44,14 +45,14 @@ class Arc(Shape, metaclass=AutoSlots):
|
||||
|
||||
# radius properties
|
||||
@property
|
||||
def radii(self) -> numpy.ndarray:
|
||||
def radii(self) -> Any: #TODO mypy#3004 NDArray[numpy.float64]:
|
||||
"""
|
||||
Return the radii `[rx, ry]`
|
||||
"""
|
||||
return self._radii
|
||||
|
||||
@radii.setter
|
||||
def radii(self, val: vector2) -> None:
|
||||
def radii(self, val: ArrayLike) -> None:
|
||||
val = numpy.array(val, dtype=float).flatten()
|
||||
if not val.size == 2:
|
||||
raise PatternError('Radii must have length 2')
|
||||
@ -81,7 +82,7 @@ class Arc(Shape, metaclass=AutoSlots):
|
||||
|
||||
# arc start/stop angle properties
|
||||
@property
|
||||
def angles(self) -> numpy.ndarray:
|
||||
def angles(self) -> Any: #TODO mypy#3004 NDArray[numpy.float64]:
|
||||
"""
|
||||
Return the start and stop angles `[a_start, a_stop]`.
|
||||
Angles are measured from x-axis after rotation
|
||||
@ -92,7 +93,7 @@ class Arc(Shape, metaclass=AutoSlots):
|
||||
return self._angles
|
||||
|
||||
@angles.setter
|
||||
def angles(self, val: vector2) -> None:
|
||||
def angles(self, val: ArrayLike) -> None:
|
||||
val = numpy.array(val, dtype=float).flatten()
|
||||
if not val.size == 2:
|
||||
raise PatternError('Angles must have length 2')
|
||||
@ -152,13 +153,13 @@ class Arc(Shape, metaclass=AutoSlots):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
radii: vector2,
|
||||
angles: vector2,
|
||||
radii: ArrayLike,
|
||||
angles: ArrayLike,
|
||||
width: float,
|
||||
*,
|
||||
poly_num_points: Optional[int] = DEFAULT_POLY_NUM_POINTS,
|
||||
poly_max_arclen: Optional[float] = None,
|
||||
offset: vector2 = (0.0, 0.0),
|
||||
offset: ArrayLike = (0.0, 0.0),
|
||||
rotation: float = 0,
|
||||
mirrored: Sequence[bool] = (False, False),
|
||||
layer: layer_t = 0,
|
||||
@ -171,6 +172,9 @@ class Arc(Shape, metaclass=AutoSlots):
|
||||
LockableImpl.unlock(self)
|
||||
self.identifier = ()
|
||||
if raw:
|
||||
assert(isinstance(radii, numpy.ndarray))
|
||||
assert(isinstance(angles, numpy.ndarray))
|
||||
assert(isinstance(offset, numpy.ndarray))
|
||||
self._radii = radii
|
||||
self._angles = angles
|
||||
self._width = width
|
||||
@ -241,7 +245,7 @@ class Arc(Shape, metaclass=AutoSlots):
|
||||
|
||||
wh = self.width / 2.0
|
||||
if wh == r0 and r0 == r1:
|
||||
thetas_inner = [0] # Don't generate multiple vertices if we're at the origin
|
||||
thetas_inner = numpy.zeros(1) # Don't generate multiple vertices if we're at the origin
|
||||
else:
|
||||
thetas_inner = numpy.linspace(a_ranges[0][1], a_ranges[0][0], num_points, endpoint=True)
|
||||
thetas_outer = numpy.linspace(a_ranges[1][0], a_ranges[1][1], num_points, endpoint=True)
|
||||
@ -261,7 +265,7 @@ class Arc(Shape, metaclass=AutoSlots):
|
||||
poly = Polygon(xys, dose=self.dose, layer=self.layer, offset=self.offset, rotation=self.rotation)
|
||||
return [poly]
|
||||
|
||||
def get_bounds(self) -> numpy.ndarray:
|
||||
def get_bounds(self) -> NDArray[numpy.float64]:
|
||||
'''
|
||||
Equation for rotated ellipse is
|
||||
`x = x0 + a * cos(t) * cos(rot) - b * sin(t) * sin(phi)`
|
||||
@ -367,7 +371,7 @@ class Arc(Shape, metaclass=AutoSlots):
|
||||
(self.offset, scale / norm_value, rotation, False, self.dose),
|
||||
lambda: Arc(radii=radii * norm_value, angles=angles, width=width * norm_value, layer=self.layer))
|
||||
|
||||
def get_cap_edges(self) -> numpy.ndarray:
|
||||
def get_cap_edges(self) -> NDArray[numpy.float64]:
|
||||
'''
|
||||
Returns:
|
||||
```
|
||||
@ -397,7 +401,7 @@ class Arc(Shape, metaclass=AutoSlots):
|
||||
maxs.append([xp, yp])
|
||||
return numpy.array([mins, maxs]) + self.offset
|
||||
|
||||
def _angles_to_parameters(self) -> numpy.ndarray:
|
||||
def _angles_to_parameters(self) -> NDArray[numpy.float64]:
|
||||
'''
|
||||
Returns:
|
||||
"Eccentric anomaly" parameter ranges for the inner and outer edges, in the form
|
||||
|
@ -1,13 +1,14 @@
|
||||
from typing import List, Dict, Optional
|
||||
import copy
|
||||
|
||||
import numpy # type: ignore
|
||||
import numpy
|
||||
from numpy import pi
|
||||
from numpy.typing import NDArray, ArrayLike
|
||||
|
||||
from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS
|
||||
from .. import PatternError
|
||||
from ..repetition import Repetition
|
||||
from ..utils import is_scalar, vector2, layer_t, AutoSlots, annotations_t
|
||||
from ..utils import is_scalar, layer_t, AutoSlots, annotations_t
|
||||
from ..traits import LockableImpl
|
||||
|
||||
|
||||
@ -48,7 +49,7 @@ class Circle(Shape, metaclass=AutoSlots):
|
||||
*,
|
||||
poly_num_points: Optional[int] = DEFAULT_POLY_NUM_POINTS,
|
||||
poly_max_arclen: Optional[float] = None,
|
||||
offset: vector2 = (0.0, 0.0),
|
||||
offset: ArrayLike = (0.0, 0.0),
|
||||
layer: layer_t = 0,
|
||||
dose: float = 1.0,
|
||||
repetition: Optional[Repetition] = None,
|
||||
@ -59,6 +60,7 @@ class Circle(Shape, metaclass=AutoSlots):
|
||||
LockableImpl.unlock(self)
|
||||
self.identifier = ()
|
||||
if raw:
|
||||
assert(isinstance(offset, numpy.ndarray))
|
||||
self._radius = radius
|
||||
self._offset = offset
|
||||
self._repetition = repetition
|
||||
@ -98,7 +100,7 @@ class Circle(Shape, metaclass=AutoSlots):
|
||||
raise PatternError('Number of points and arclength left '
|
||||
'unspecified (default was also overridden)')
|
||||
|
||||
n = []
|
||||
n: List[float] = []
|
||||
if poly_num_points is not None:
|
||||
n += [poly_num_points]
|
||||
if poly_max_arclen is not None:
|
||||
@ -111,7 +113,7 @@ class Circle(Shape, metaclass=AutoSlots):
|
||||
|
||||
return [Polygon(xys, offset=self.offset, dose=self.dose, layer=self.layer)]
|
||||
|
||||
def get_bounds(self) -> numpy.ndarray:
|
||||
def get_bounds(self) -> NDArray[numpy.float64]:
|
||||
return numpy.vstack((self.offset - self.radius,
|
||||
self.offset + self.radius))
|
||||
|
||||
|
@ -1,14 +1,15 @@
|
||||
from typing import List, Dict, Sequence, Optional
|
||||
from typing import List, Dict, Sequence, Optional, Any
|
||||
import copy
|
||||
import math
|
||||
|
||||
import numpy # type: ignore
|
||||
import numpy
|
||||
from numpy import pi
|
||||
from numpy.typing import ArrayLike, NDArray
|
||||
|
||||
from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS
|
||||
from .. import PatternError
|
||||
from ..repetition import Repetition
|
||||
from ..utils import is_scalar, rotation_matrix_2d, vector2, layer_t, AutoSlots, annotations_t
|
||||
from ..utils import is_scalar, rotation_matrix_2d, layer_t, AutoSlots, annotations_t
|
||||
from ..traits import LockableImpl
|
||||
|
||||
|
||||
@ -20,7 +21,7 @@ class Ellipse(Shape, metaclass=AutoSlots):
|
||||
__slots__ = ('_radii', '_rotation',
|
||||
'poly_num_points', 'poly_max_arclen')
|
||||
|
||||
_radii: numpy.ndarray
|
||||
_radii: NDArray[numpy.float64]
|
||||
""" Ellipse radii """
|
||||
|
||||
_rotation: float
|
||||
@ -34,14 +35,14 @@ class Ellipse(Shape, metaclass=AutoSlots):
|
||||
|
||||
# radius properties
|
||||
@property
|
||||
def radii(self) -> numpy.ndarray:
|
||||
def radii(self) -> Any: #TODO mypy#3004 NDArray[numpy.float64]:
|
||||
"""
|
||||
Return the radii `[rx, ry]`
|
||||
"""
|
||||
return self._radii
|
||||
|
||||
@radii.setter
|
||||
def radii(self, val: vector2) -> None:
|
||||
def radii(self, val: ArrayLike) -> None:
|
||||
val = numpy.array(val).flatten()
|
||||
if not val.size == 2:
|
||||
raise PatternError('Radii must have length 2')
|
||||
@ -89,11 +90,11 @@ class Ellipse(Shape, metaclass=AutoSlots):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
radii: vector2,
|
||||
radii: ArrayLike,
|
||||
*,
|
||||
poly_num_points: Optional[int] = DEFAULT_POLY_NUM_POINTS,
|
||||
poly_max_arclen: Optional[float] = None,
|
||||
offset: vector2 = (0.0, 0.0),
|
||||
offset: ArrayLike = (0.0, 0.0),
|
||||
rotation: float = 0,
|
||||
mirrored: Sequence[bool] = (False, False),
|
||||
layer: layer_t = 0,
|
||||
@ -106,6 +107,8 @@ class Ellipse(Shape, metaclass=AutoSlots):
|
||||
LockableImpl.unlock(self)
|
||||
self.identifier = ()
|
||||
if raw:
|
||||
assert(isinstance(radii, numpy.ndarray))
|
||||
assert(isinstance(offset, numpy.ndarray))
|
||||
self._radii = radii
|
||||
self._offset = offset
|
||||
self._rotation = rotation
|
||||
@ -173,7 +176,7 @@ class Ellipse(Shape, metaclass=AutoSlots):
|
||||
poly = Polygon(xys, dose=self.dose, layer=self.layer, offset=self.offset, rotation=self.rotation)
|
||||
return [poly]
|
||||
|
||||
def get_bounds(self) -> numpy.ndarray:
|
||||
def get_bounds(self) -> NDArray[numpy.float64]:
|
||||
rot_radii = numpy.dot(rotation_matrix_2d(self.rotation), self.radii)
|
||||
return numpy.vstack((self.offset - rot_radii[0],
|
||||
self.offset + rot_radii[1]))
|
||||
|
@ -1,15 +1,15 @@
|
||||
from typing import List, Tuple, Dict, Optional, Sequence
|
||||
from typing import List, Tuple, Dict, Optional, Sequence, Any
|
||||
import copy
|
||||
from enum import Enum
|
||||
|
||||
import numpy # type: ignore
|
||||
import numpy
|
||||
from numpy import pi, inf
|
||||
from numpy.typing import ArrayLike
|
||||
from numpy.typing import NDArray, ArrayLike
|
||||
|
||||
from . import Shape, normalized_shape_tuple, Polygon, Circle
|
||||
from .. import PatternError
|
||||
from ..repetition import Repetition
|
||||
from ..utils import is_scalar, rotation_matrix_2d, vector2, layer_t, AutoSlots
|
||||
from ..utils import is_scalar, rotation_matrix_2d, layer_t, AutoSlots
|
||||
from ..utils import remove_colinear_vertices, remove_duplicate_vertices, annotations_t
|
||||
from ..traits import LockableImpl
|
||||
|
||||
@ -30,10 +30,10 @@ class Path(Shape, metaclass=AutoSlots):
|
||||
A normalized_form(...) is available, but can be quite slow with lots of vertices.
|
||||
"""
|
||||
__slots__ = ('_vertices', '_width', '_cap', '_cap_extensions')
|
||||
_vertices: numpy.ndarray
|
||||
_vertices: NDArray[numpy.float64]
|
||||
_width: float
|
||||
_cap: PathCap
|
||||
_cap_extensions: Optional[numpy.ndarray]
|
||||
_cap_extensions: Optional[NDArray[numpy.float64]]
|
||||
|
||||
Cap = PathCap
|
||||
|
||||
@ -73,7 +73,7 @@ class Path(Shape, metaclass=AutoSlots):
|
||||
|
||||
# cap_extensions property
|
||||
@property
|
||||
def cap_extensions(self) -> Optional[numpy.ndarray]:
|
||||
def cap_extensions(self) -> Optional[Any]: #TODO mypy#3004 NDArray[numpy.float64]]:
|
||||
"""
|
||||
Path end-cap extension
|
||||
|
||||
@ -83,7 +83,7 @@ class Path(Shape, metaclass=AutoSlots):
|
||||
return self._cap_extensions
|
||||
|
||||
@cap_extensions.setter
|
||||
def cap_extensions(self, vals: Optional[numpy.ndarray]) -> None:
|
||||
def cap_extensions(self, vals: Optional[ArrayLike]) -> None:
|
||||
custom_caps = (PathCap.SquareCustom,)
|
||||
if self.cap in custom_caps:
|
||||
if vals is None:
|
||||
@ -96,7 +96,7 @@ class Path(Shape, metaclass=AutoSlots):
|
||||
|
||||
# vertices property
|
||||
@property
|
||||
def vertices(self) -> numpy.ndarray:
|
||||
def vertices(self) -> Any: #TODO mypy#3004 NDArray[numpy.float64]]:
|
||||
"""
|
||||
Vertices of the path (Nx2 ndarray: `[[x0, y0], [x1, y1], ...]`)
|
||||
"""
|
||||
@ -113,7 +113,7 @@ class Path(Shape, metaclass=AutoSlots):
|
||||
|
||||
# xs property
|
||||
@property
|
||||
def xs(self) -> numpy.ndarray:
|
||||
def xs(self) -> NDArray[numpy.float64]:
|
||||
"""
|
||||
All vertex x coords as a 1D ndarray
|
||||
"""
|
||||
@ -128,7 +128,7 @@ class Path(Shape, metaclass=AutoSlots):
|
||||
|
||||
# ys property
|
||||
@property
|
||||
def ys(self) -> numpy.ndarray:
|
||||
def ys(self) -> NDArray[numpy.float64]:
|
||||
"""
|
||||
All vertex y coords as a 1D ndarray
|
||||
"""
|
||||
@ -148,7 +148,7 @@ class Path(Shape, metaclass=AutoSlots):
|
||||
*,
|
||||
cap: PathCap = PathCap.Flush,
|
||||
cap_extensions: Optional[ArrayLike] = None,
|
||||
offset: vector2 = (0.0, 0.0),
|
||||
offset: ArrayLike = (0.0, 0.0),
|
||||
rotation: float = 0,
|
||||
mirrored: Sequence[bool] = (False, False),
|
||||
layer: layer_t = 0,
|
||||
@ -163,6 +163,9 @@ class Path(Shape, metaclass=AutoSlots):
|
||||
|
||||
self.identifier = ()
|
||||
if raw:
|
||||
assert(isinstance(vertices, numpy.ndarray))
|
||||
assert(isinstance(offset, numpy.ndarray))
|
||||
assert(isinstance(cap_extensions, numpy.ndarray) or cap_extensions is None)
|
||||
self._vertices = vertices
|
||||
self._offset = offset
|
||||
self._repetition = repetition
|
||||
@ -199,11 +202,11 @@ class Path(Shape, metaclass=AutoSlots):
|
||||
|
||||
@staticmethod
|
||||
def travel(
|
||||
travel_pairs: Tuple[Tuple[float, float]],
|
||||
travel_pairs: Sequence[Tuple[float, float]],
|
||||
width: float = 0.0,
|
||||
cap: PathCap = PathCap.Flush,
|
||||
cap_extensions: Optional[Tuple[float, float]] = None,
|
||||
offset: vector2 = (0.0, 0.0),
|
||||
offset: ArrayLike = (0.0, 0.0),
|
||||
rotation: float = 0,
|
||||
mirrored: Sequence[bool] = (False, False),
|
||||
layer: layer_t = 0,
|
||||
@ -236,7 +239,7 @@ class Path(Shape, metaclass=AutoSlots):
|
||||
#TODO: needs testing
|
||||
direction = numpy.array([1, 0])
|
||||
|
||||
verts = [[0, 0]]
|
||||
verts = [numpy.zeros(2)]
|
||||
for angle, distance in travel_pairs:
|
||||
direction = numpy.dot(rotation_matrix_2d(angle), direction.T).T
|
||||
verts.append(verts[-1] + direction * distance)
|
||||
@ -319,7 +322,7 @@ class Path(Shape, metaclass=AutoSlots):
|
||||
|
||||
return polys
|
||||
|
||||
def get_bounds(self) -> numpy.ndarray:
|
||||
def get_bounds(self) -> NDArray[numpy.float64]:
|
||||
if self.cap == PathCap.Circle:
|
||||
bounds = self.offset + numpy.vstack((numpy.min(self.vertices, axis=0) - self.width / 2,
|
||||
numpy.max(self.vertices, axis=0) + self.width / 2))
|
||||
@ -409,10 +412,11 @@ class Path(Shape, metaclass=AutoSlots):
|
||||
self.vertices = remove_colinear_vertices(self.vertices, closed_path=False)
|
||||
return self
|
||||
|
||||
def _calculate_cap_extensions(self) -> numpy.ndarray:
|
||||
def _calculate_cap_extensions(self) -> NDArray[numpy.float64]:
|
||||
if self.cap == PathCap.Square:
|
||||
extensions = numpy.full(2, self.width / 2)
|
||||
elif self.cap == PathCap.SquareCustom:
|
||||
assert(isinstance(self.cap_extensions, numpy.ndarray))
|
||||
extensions = self.cap_extensions
|
||||
else:
|
||||
# Flush or Circle
|
||||
|
@ -1,14 +1,14 @@
|
||||
from typing import List, Dict, Optional, Sequence
|
||||
from typing import List, Dict, Optional, Sequence, Any
|
||||
import copy
|
||||
|
||||
import numpy # type: ignore
|
||||
import numpy
|
||||
from numpy import pi
|
||||
from numpy.typing import ArrayLike
|
||||
from numpy.typing import NDArray, ArrayLike
|
||||
|
||||
from . import Shape, normalized_shape_tuple
|
||||
from .. import PatternError
|
||||
from ..repetition import Repetition
|
||||
from ..utils import is_scalar, rotation_matrix_2d, vector2, layer_t, AutoSlots
|
||||
from ..utils import is_scalar, rotation_matrix_2d, layer_t, AutoSlots
|
||||
from ..utils import remove_colinear_vertices, remove_duplicate_vertices, annotations_t
|
||||
from ..traits import LockableImpl
|
||||
|
||||
@ -22,12 +22,12 @@ class Polygon(Shape, metaclass=AutoSlots):
|
||||
"""
|
||||
__slots__ = ('_vertices',)
|
||||
|
||||
_vertices: numpy.ndarray
|
||||
_vertices: NDArray[numpy.float64]
|
||||
""" Nx2 ndarray of vertices `[[x0, y0], [x1, y1], ...]` """
|
||||
|
||||
# vertices property
|
||||
@property
|
||||
def vertices(self) -> numpy.ndarray:
|
||||
def vertices(self) -> Any: #TODO mypy#3004 NDArray[numpy.float64]:
|
||||
"""
|
||||
Vertices of the polygon (Nx2 ndarray: `[[x0, y0], [x1, y1], ...]`)
|
||||
"""
|
||||
@ -44,7 +44,7 @@ class Polygon(Shape, metaclass=AutoSlots):
|
||||
|
||||
# xs property
|
||||
@property
|
||||
def xs(self) -> numpy.ndarray:
|
||||
def xs(self) -> NDArray[numpy.float64]:
|
||||
"""
|
||||
All vertex x coords as a 1D ndarray
|
||||
"""
|
||||
@ -59,7 +59,7 @@ class Polygon(Shape, metaclass=AutoSlots):
|
||||
|
||||
# ys property
|
||||
@property
|
||||
def ys(self) -> numpy.ndarray:
|
||||
def ys(self) -> NDArray[numpy.float64]:
|
||||
"""
|
||||
All vertex y coords as a 1D ndarray
|
||||
"""
|
||||
@ -76,7 +76,7 @@ class Polygon(Shape, metaclass=AutoSlots):
|
||||
self,
|
||||
vertices: ArrayLike,
|
||||
*,
|
||||
offset: vector2 = (0.0, 0.0),
|
||||
offset: ArrayLike = (0.0, 0.0),
|
||||
rotation: float = 0.0,
|
||||
mirrored: Sequence[bool] = (False, False),
|
||||
layer: layer_t = 0,
|
||||
@ -89,6 +89,8 @@ class Polygon(Shape, metaclass=AutoSlots):
|
||||
LockableImpl.unlock(self)
|
||||
self.identifier = ()
|
||||
if raw:
|
||||
assert(isinstance(vertices, numpy.ndarray))
|
||||
assert(isinstance(offset, numpy.ndarray))
|
||||
self._vertices = vertices
|
||||
self._offset = offset
|
||||
self._repetition = repetition
|
||||
@ -120,7 +122,7 @@ class Polygon(Shape, metaclass=AutoSlots):
|
||||
side_length: float,
|
||||
*,
|
||||
rotation: float = 0.0,
|
||||
offset: vector2 = (0.0, 0.0),
|
||||
offset: ArrayLike = (0.0, 0.0),
|
||||
layer: layer_t = 0,
|
||||
dose: float = 1.0,
|
||||
repetition: Optional[Repetition] = None,
|
||||
@ -155,7 +157,7 @@ class Polygon(Shape, metaclass=AutoSlots):
|
||||
ly: float,
|
||||
*,
|
||||
rotation: float = 0,
|
||||
offset: vector2 = (0.0, 0.0),
|
||||
offset: ArrayLike = (0.0, 0.0),
|
||||
layer: layer_t = 0,
|
||||
dose: float = 1.0,
|
||||
repetition: Optional[Repetition] = None,
|
||||
@ -291,7 +293,7 @@ class Polygon(Shape, metaclass=AutoSlots):
|
||||
side_length: Optional[float] = None,
|
||||
inner_radius: Optional[float] = None,
|
||||
regular: bool = True,
|
||||
center: vector2 = (0.0, 0.0),
|
||||
center: ArrayLike = (0.0, 0.0),
|
||||
rotation: float = 0.0,
|
||||
layer: layer_t = 0,
|
||||
dose: float = 1.0,
|
||||
@ -353,7 +355,7 @@ class Polygon(Shape, metaclass=AutoSlots):
|
||||
) -> List['Polygon']:
|
||||
return [copy.deepcopy(self)]
|
||||
|
||||
def get_bounds(self) -> numpy.ndarray:
|
||||
def get_bounds(self) -> NDArray[numpy.float64]:
|
||||
return numpy.vstack((self.offset + numpy.min(self.vertices, axis=0),
|
||||
self.offset + numpy.max(self.vertices, axis=0)))
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
from typing import List, Tuple, Callable, TypeVar, Optional, TYPE_CHECKING
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import numpy # type: ignore
|
||||
from numpy.typing import ArrayLike
|
||||
import numpy
|
||||
from numpy.typing import NDArray, ArrayLike
|
||||
|
||||
from ..traits import (PositionableImpl, LayerableImpl, DoseableImpl,
|
||||
Rotatable, Mirrorable, Copyable, Scalable,
|
||||
@ -15,7 +15,7 @@ if TYPE_CHECKING:
|
||||
|
||||
# Type definitions
|
||||
normalized_shape_tuple = Tuple[Tuple,
|
||||
Tuple[numpy.ndarray, float, float, bool, float],
|
||||
Tuple[NDArray[numpy.float64], float, float, bool, float],
|
||||
Callable[[], 'Shape']]
|
||||
|
||||
# ## Module-wide defaults
|
||||
@ -117,12 +117,16 @@ class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable
|
||||
"""
|
||||
from . import Polygon
|
||||
|
||||
grid_x = numpy.unique(grid_x)
|
||||
grid_y = numpy.unique(grid_y)
|
||||
gx = numpy.unique(grid_x)
|
||||
gy = numpy.unique(grid_y)
|
||||
|
||||
polygon_contours = []
|
||||
for polygon in self.to_polygons():
|
||||
mins, maxs = polygon.get_bounds()
|
||||
bounds = polygon.get_bounds()
|
||||
if not bounds:
|
||||
continue
|
||||
|
||||
mins, maxs = bounds
|
||||
|
||||
vertex_lists = []
|
||||
p_verts = polygon.vertices + polygon.offset
|
||||
@ -130,12 +134,12 @@ class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable
|
||||
dv = v_next - v
|
||||
|
||||
# Find x-index bounds for the line # TODO: fix this and err_xmin/xmax for grids smaller than the line / shape
|
||||
gxi_range = numpy.digitize([v[0], v_next[0]], grid_x)
|
||||
gxi_min = numpy.min(gxi_range - 1).clip(0, len(grid_x) - 1)
|
||||
gxi_max = numpy.max(gxi_range).clip(0, len(grid_x))
|
||||
gxi_range = numpy.digitize([v[0], v_next[0]], gx)
|
||||
gxi_min = numpy.min(gxi_range - 1).clip(0, len(gx) - 1)
|
||||
gxi_max = numpy.max(gxi_range).clip(0, len(gx))
|
||||
|
||||
err_xmin = (min(v[0], v_next[0]) - grid_x[gxi_min]) / (grid_x[gxi_min + 1] - grid_x[gxi_min])
|
||||
err_xmax = (max(v[0], v_next[0]) - grid_x[gxi_max - 1]) / (grid_x[gxi_max] - grid_x[gxi_max - 1])
|
||||
err_xmin = (min(v[0], v_next[0]) - gx[gxi_min]) / (gx[gxi_min + 1] - gx[gxi_min])
|
||||
err_xmax = (max(v[0], v_next[0]) - gx[gxi_max - 1]) / (gx[gxi_max] - gx[gxi_max - 1])
|
||||
|
||||
if err_xmin >= 0.5:
|
||||
gxi_min += 1
|
||||
@ -146,32 +150,32 @@ class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable
|
||||
# Vertical line, don't calculate slope
|
||||
xi = [gxi_min, gxi_max - 1]
|
||||
ys = numpy.array([v[1], v_next[1]])
|
||||
yi = numpy.digitize(ys, grid_y).clip(1, len(grid_y) - 1)
|
||||
err_y = (ys - grid_y[yi]) / (grid_y[yi] - grid_y[yi - 1])
|
||||
yi = numpy.digitize(ys, gy).clip(1, len(gy) - 1)
|
||||
err_y = (ys - gy[yi]) / (gy[yi] - gy[yi - 1])
|
||||
yi[err_y < 0.5] -= 1
|
||||
|
||||
segment = numpy.column_stack((grid_x[xi], grid_y[yi]))
|
||||
segment = numpy.column_stack((gx[xi], gy[yi]))
|
||||
vertex_lists.append(segment)
|
||||
continue
|
||||
|
||||
m = dv[1] / dv[0]
|
||||
|
||||
def get_grid_inds(xes: numpy.ndarray) -> numpy.ndarray:
|
||||
def get_grid_inds(xes: ArrayLike) -> NDArray[numpy.float64]:
|
||||
ys = m * (xes - v[0]) + v[1]
|
||||
|
||||
# (inds - 1) is the index of the y-grid line below the edge's intersection with the x-grid
|
||||
inds = numpy.digitize(ys, grid_y).clip(1, len(grid_y) - 1)
|
||||
inds = numpy.digitize(ys, gy).clip(1, len(gy) - 1)
|
||||
|
||||
# err is what fraction of the cell upwards we have to go to reach our y
|
||||
# (can be negative at bottom edge due to clip above)
|
||||
err = (ys - grid_y[inds - 1]) / (grid_y[inds] - grid_y[inds - 1])
|
||||
err = (ys - gy[inds - 1]) / (gy[inds] - gy[inds - 1])
|
||||
|
||||
# now set inds to the index of the nearest y-grid line
|
||||
inds[err < 0.5] -= 1
|
||||
return inds
|
||||
|
||||
# Find the y indices on all x gridlines
|
||||
xs = grid_x[gxi_min:gxi_max]
|
||||
xs = gx[gxi_min:gxi_max]
|
||||
inds = get_grid_inds(xs)
|
||||
|
||||
# Find y-intersections for x-midpoints
|
||||
@ -186,7 +190,7 @@ class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable
|
||||
yinds[1::3] = inds2
|
||||
yinds[2::3] = inds2
|
||||
|
||||
vlist = numpy.column_stack((grid_x[xinds], grid_y[yinds]))
|
||||
vlist = numpy.column_stack((gx[xinds], gy[yinds]))
|
||||
if dv[0] < 0:
|
||||
vlist = vlist[::-1]
|
||||
|
||||
@ -249,23 +253,27 @@ class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable
|
||||
import skimage.measure # type: ignore
|
||||
import float_raster
|
||||
|
||||
grid_x = numpy.unique(grid_x)
|
||||
grid_y = numpy.unique(grid_y)
|
||||
grx = numpy.unique(grid_x)
|
||||
gry = numpy.unique(grid_y)
|
||||
|
||||
polygon_contours = []
|
||||
for polygon in self.to_polygons():
|
||||
# Get rid of unused gridlines (anything not within 2 lines of the polygon bounds)
|
||||
mins, maxs = polygon.get_bounds()
|
||||
keep_x = numpy.logical_and(grid_x > mins[0], grid_x < maxs[0])
|
||||
keep_y = numpy.logical_and(grid_y > mins[1], grid_y < maxs[1])
|
||||
bounds = polygon.get_bounds()
|
||||
if not bounds:
|
||||
continue
|
||||
|
||||
mins, maxs = bounds
|
||||
keep_x = numpy.logical_and(grx > mins[0], grx < maxs[0])
|
||||
keep_y = numpy.logical_and(gry > mins[1], gry < maxs[1])
|
||||
for k in (keep_x, keep_y):
|
||||
for s in (1, 2):
|
||||
k[s:] += k[:-s]
|
||||
k[:-s] += k[s:]
|
||||
k = k > 0
|
||||
|
||||
gx = grid_x[keep_x]
|
||||
gy = grid_y[keep_y]
|
||||
gx = grx[keep_x]
|
||||
gy = gry[keep_y]
|
||||
|
||||
if len(gx) == 0 or len(gy) == 0:
|
||||
continue
|
||||
@ -286,8 +294,8 @@ class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable
|
||||
# /2 deals with supersampling
|
||||
# +.5 deals with the fact that our 0-edge becomes -.5 in the super-sampled contour output
|
||||
snapped_contour = numpy.round((contour + .5) / 2).astype(int)
|
||||
vertices = numpy.hstack((grid_x[snapped_contour[:, None, 0] + offset_i[0]],
|
||||
grid_y[snapped_contour[:, None, 1] + offset_i[1]]))
|
||||
vertices = numpy.hstack((grx[snapped_contour[:, None, 0] + offset_i[0]],
|
||||
gry[snapped_contour[:, None, 1] + offset_i[1]]))
|
||||
|
||||
manhattan_polygons.append(Polygon(
|
||||
vertices=vertices,
|
||||
|
@ -1,14 +1,15 @@
|
||||
from typing import List, Tuple, Dict, Sequence, Optional
|
||||
from typing import List, Tuple, Dict, Sequence, Optional, Any
|
||||
import copy
|
||||
|
||||
import numpy # type: ignore
|
||||
import numpy
|
||||
from numpy import pi, inf
|
||||
from numpy.typing import NDArray, ArrayLike
|
||||
|
||||
from . import Shape, Polygon, normalized_shape_tuple
|
||||
from .. import PatternError
|
||||
from ..repetition import Repetition
|
||||
from ..traits import RotatableImpl
|
||||
from ..utils import is_scalar, vector2, get_bit, normalize_mirror, layer_t, AutoSlots
|
||||
from ..utils import is_scalar, get_bit, normalize_mirror, layer_t, AutoSlots
|
||||
from ..utils import annotations_t
|
||||
from ..traits import LockableImpl
|
||||
|
||||
@ -26,7 +27,7 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
|
||||
|
||||
_string: str
|
||||
_height: float
|
||||
_mirrored: numpy.ndarray # ndarray[bool]
|
||||
_mirrored: NDArray[numpy.bool_]
|
||||
font_path: str
|
||||
|
||||
# vertices property
|
||||
@ -51,7 +52,7 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
|
||||
|
||||
# Mirrored property
|
||||
@property
|
||||
def mirrored(self) -> numpy.ndarray: # ndarray[bool]
|
||||
def mirrored(self) -> Any: #TODO mypy#3004 NDArray[numpy.bool_]:
|
||||
return self._mirrored
|
||||
|
||||
@mirrored.setter
|
||||
@ -66,9 +67,9 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
|
||||
height: float,
|
||||
font_path: str,
|
||||
*,
|
||||
offset: vector2 = (0.0, 0.0),
|
||||
offset: ArrayLike = (0.0, 0.0),
|
||||
rotation: float = 0.0,
|
||||
mirrored: Tuple[bool, bool] = (False, False),
|
||||
mirrored: ArrayLike = (False, False),
|
||||
layer: layer_t = 0,
|
||||
dose: float = 1.0,
|
||||
repetition: Optional[Repetition] = None,
|
||||
@ -79,6 +80,8 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
|
||||
LockableImpl.unlock(self)
|
||||
self.identifier = ()
|
||||
if raw:
|
||||
assert(isinstance(offset, numpy.ndarray))
|
||||
assert(isinstance(mirrored, numpy.ndarray))
|
||||
self._offset = offset
|
||||
self._layer = layer
|
||||
self._dose = dose
|
||||
@ -155,7 +158,7 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
|
||||
mirrored=(mirror_x, False),
|
||||
layer=self.layer))
|
||||
|
||||
def get_bounds(self) -> numpy.ndarray:
|
||||
def get_bounds(self) -> NDArray[numpy.float64]:
|
||||
# rotation makes this a huge pain when using slot.advance and glyph.bbox(), so
|
||||
# just convert to polygons instead
|
||||
bounds = numpy.array([[+inf, +inf], [-inf, -inf]])
|
||||
@ -201,7 +204,7 @@ def get_char_as_polygons(
|
||||
outline = slot.outline
|
||||
|
||||
start = 0
|
||||
all_verts, all_codes = [], []
|
||||
all_verts_list, all_codes = [], []
|
||||
for end in outline.contours:
|
||||
points = outline.points[start:end + 1]
|
||||
points.append(points[0])
|
||||
@ -238,11 +241,11 @@ def get_char_as_polygons(
|
||||
codes.extend([Path.CURVE3, Path.CURVE3])
|
||||
verts.append(segment[-1])
|
||||
codes.append(Path.CURVE3)
|
||||
all_verts.extend(verts)
|
||||
all_verts_list.extend(verts)
|
||||
all_codes.extend(codes)
|
||||
start = end + 1
|
||||
|
||||
all_verts = numpy.array(all_verts) / resolution
|
||||
all_verts = numpy.array(all_verts_list) / resolution
|
||||
|
||||
advance = slot.advance.x / resolution
|
||||
|
||||
|
@ -7,11 +7,12 @@
|
||||
from typing import Dict, Tuple, Optional, Sequence, TYPE_CHECKING, Any, TypeVar
|
||||
import copy
|
||||
|
||||
import numpy # type: ignore
|
||||
import numpy
|
||||
from numpy import pi
|
||||
from numpy.typing import NDArray, ArrayLike
|
||||
|
||||
from .error import PatternError
|
||||
from .utils import is_scalar, vector2, AutoSlots, annotations_t
|
||||
from .utils import is_scalar, AutoSlots, annotations_t
|
||||
from .repetition import Repetition
|
||||
from .traits import (PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl,
|
||||
Mirrorable, PivotableImpl, Copyable, LockableImpl, RepeatableImpl,
|
||||
@ -40,7 +41,7 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
||||
_pattern: Optional['Pattern']
|
||||
""" The `Pattern` being instanced """
|
||||
|
||||
_mirrored: numpy.ndarray # ndarray[bool]
|
||||
_mirrored: NDArray[numpy.bool_]
|
||||
""" Whether to mirror the instance across the x and/or y axes. """
|
||||
|
||||
identifier: Tuple[Any, ...]
|
||||
@ -50,7 +51,7 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
||||
self,
|
||||
pattern: Optional['Pattern'],
|
||||
*,
|
||||
offset: vector2 = (0.0, 0.0),
|
||||
offset: ArrayLike = (0.0, 0.0),
|
||||
rotation: float = 0.0,
|
||||
mirrored: Optional[Sequence[bool]] = None,
|
||||
dose: float = 1.0,
|
||||
@ -113,7 +114,7 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
||||
return self._pattern
|
||||
|
||||
@pattern.setter
|
||||
def pattern(self, val: Optional['Pattern']):
|
||||
def pattern(self, val: Optional['Pattern']) -> None:
|
||||
from .pattern import Pattern
|
||||
if val is not None and not isinstance(val, Pattern):
|
||||
raise PatternError(f'Provided pattern {val} is not a Pattern object or None!')
|
||||
@ -121,11 +122,11 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
||||
|
||||
# Mirrored property
|
||||
@property
|
||||
def mirrored(self) -> numpy.ndarray: # ndarray[bool]
|
||||
def mirrored(self) -> Any: #TODO mypy#3004 NDArray[numpy.bool_]:
|
||||
return self._mirrored
|
||||
|
||||
@mirrored.setter
|
||||
def mirrored(self, val: Sequence[bool]):
|
||||
def mirrored(self, val: ArrayLike) -> None:
|
||||
if is_scalar(val):
|
||||
raise PatternError('Mirrored must be a 2-element list of booleans')
|
||||
self._mirrored = numpy.array(val, dtype=bool, copy=True)
|
||||
@ -167,7 +168,7 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
||||
self.repetition.mirror(axis)
|
||||
return self
|
||||
|
||||
def get_bounds(self) -> Optional[numpy.ndarray]:
|
||||
def get_bounds(self) -> Optional[NDArray[numpy.float64]]:
|
||||
"""
|
||||
Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the
|
||||
extent of the `SubPattern` in each dimension.
|
||||
|
@ -1,11 +1,12 @@
|
||||
# TODO top-level comment about how traits should set __slots__ = (), and how to use AutoSlots
|
||||
|
||||
from typing import TypeVar
|
||||
from typing import TypeVar, Any, Optional
|
||||
from abc import ABCMeta, abstractmethod
|
||||
import numpy # type: ignore
|
||||
|
||||
import numpy
|
||||
from numpy.typing import NDArray, ArrayLike
|
||||
|
||||
from ..error import MasqueError
|
||||
from ..utils import vector2
|
||||
|
||||
|
||||
T = TypeVar('T', bound='Positionable')
|
||||
@ -23,7 +24,7 @@ class Positionable(metaclass=ABCMeta):
|
||||
'''
|
||||
@property
|
||||
@abstractmethod
|
||||
def offset(self) -> numpy.ndarray:
|
||||
def offset(self) -> NDArray[numpy.float64]:
|
||||
"""
|
||||
[x, y] offset
|
||||
"""
|
||||
@ -31,21 +32,11 @@ class Positionable(metaclass=ABCMeta):
|
||||
|
||||
# @offset.setter
|
||||
# @abstractmethod
|
||||
# def offset(self, val: vector2):
|
||||
# def offset(self, val: ArrayLike):
|
||||
# pass
|
||||
|
||||
'''
|
||||
--- Abstract methods
|
||||
'''
|
||||
@abstractmethod
|
||||
def get_bounds(self) -> numpy.ndarray:
|
||||
"""
|
||||
Returns `[[x_min, y_min], [x_max, y_max]]` which specify a minimal bounding box for the entity.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def set_offset(self: T, offset: vector2) -> T:
|
||||
def set_offset(self: T, offset: ArrayLike) -> T:
|
||||
"""
|
||||
Set the offset
|
||||
|
||||
@ -58,7 +49,7 @@ class Positionable(metaclass=ABCMeta):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def translate(self: T, offset: vector2) -> T:
|
||||
def translate(self: T, offset: ArrayLike) -> T:
|
||||
"""
|
||||
Translate the entity by the given offset
|
||||
|
||||
@ -70,6 +61,13 @@ class Positionable(metaclass=ABCMeta):
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_bounds(self) -> Optional[NDArray[numpy.float64]]:
|
||||
"""
|
||||
Returns `[[x_min, y_min], [x_max, y_max]]` which specify a minimal bounding box for the entity.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class PositionableImpl(Positionable, metaclass=ABCMeta):
|
||||
"""
|
||||
@ -77,7 +75,7 @@ class PositionableImpl(Positionable, metaclass=ABCMeta):
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
_offset: numpy.ndarray
|
||||
_offset: NDArray[numpy.float64]
|
||||
""" `[x_offset, y_offset]` """
|
||||
|
||||
'''
|
||||
@ -85,14 +83,14 @@ class PositionableImpl(Positionable, metaclass=ABCMeta):
|
||||
'''
|
||||
# offset property
|
||||
@property
|
||||
def offset(self) -> numpy.ndarray:
|
||||
def offset(self) -> Any: #TODO mypy#3003 NDArray[numpy.float64]:
|
||||
"""
|
||||
[x, y] offset
|
||||
"""
|
||||
return self._offset
|
||||
|
||||
@offset.setter
|
||||
def offset(self, val: vector2):
|
||||
def offset(self, val: ArrayLike) -> None:
|
||||
if not isinstance(val, numpy.ndarray) or val.dtype != numpy.float64:
|
||||
val = numpy.array(val, dtype=float)
|
||||
|
||||
@ -103,12 +101,12 @@ class PositionableImpl(Positionable, metaclass=ABCMeta):
|
||||
'''
|
||||
---- Methods
|
||||
'''
|
||||
def set_offset(self: I, offset: vector2) -> I:
|
||||
def set_offset(self: I, offset: ArrayLike) -> I:
|
||||
self.offset = offset
|
||||
return self
|
||||
|
||||
def translate(self: I, offset: vector2) -> I:
|
||||
self._offset += offset
|
||||
def translate(self: I, offset: ArrayLike) -> I:
|
||||
self._offset += offset # type: ignore # NDArray += ArrayLike should be fine??
|
||||
return self
|
||||
|
||||
def _lock(self: I) -> I:
|
||||
|
@ -1,12 +1,13 @@
|
||||
from typing import TypeVar
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import numpy # type: ignore
|
||||
import numpy
|
||||
from numpy import pi
|
||||
from numpy.typing import ArrayLike, NDArray
|
||||
|
||||
#from .positionable import Positionable
|
||||
from ..error import MasqueError
|
||||
from ..utils import is_scalar, rotation_matrix_2d, vector2
|
||||
from ..utils import is_scalar, rotation_matrix_2d
|
||||
|
||||
T = TypeVar('T', bound='Rotatable')
|
||||
I = TypeVar('I', bound='RotatableImpl')
|
||||
@ -89,7 +90,7 @@ class Pivotable(metaclass=ABCMeta):
|
||||
__slots__ = ()
|
||||
|
||||
@abstractmethod
|
||||
def rotate_around(self: P, pivot: vector2, rotation: float) -> P:
|
||||
def rotate_around(self: P, pivot: ArrayLike, rotation: float) -> P:
|
||||
"""
|
||||
Rotate the object around a point.
|
||||
|
||||
@ -109,11 +110,11 @@ class PivotableImpl(Pivotable, metaclass=ABCMeta):
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def rotate_around(self: J, pivot: vector2, rotation: float) -> J:
|
||||
def rotate_around(self: J, pivot: ArrayLike, rotation: float) -> J:
|
||||
pivot = numpy.array(pivot, dtype=float)
|
||||
self.translate(-pivot)
|
||||
self.rotate(rotation)
|
||||
self.offset = numpy.dot(rotation_matrix_2d(rotation), self.offset)
|
||||
self.offset = numpy.dot(rotation_matrix_2d(rotation), self.offset) #type: ignore #TODO: mypy#3004
|
||||
self.translate(+pivot)
|
||||
return self
|
||||
|
||||
|
@ -4,12 +4,11 @@ Various helper functions
|
||||
from typing import Any, Union, Tuple, Sequence, Dict, List
|
||||
from abc import ABCMeta
|
||||
|
||||
import numpy # type: ignore
|
||||
from numpy.typing import ArrayLike
|
||||
import numpy
|
||||
from numpy.typing import NDArray, ArrayLike
|
||||
|
||||
|
||||
# Type definitions
|
||||
vector2 = ArrayLike
|
||||
layer_t = Union[int, Tuple[int, int], str]
|
||||
annotations_t = Dict[str, List[Union[int, float, str]]]
|
||||
|
||||
@ -57,7 +56,7 @@ def set_bit(bit_string: Any, bit_id: int, value: bool) -> Any:
|
||||
return bit_string
|
||||
|
||||
|
||||
def rotation_matrix_2d(theta: float) -> numpy.ndarray:
|
||||
def rotation_matrix_2d(theta: float) -> NDArray[numpy.float64]:
|
||||
"""
|
||||
2D rotation matrix for rotating counterclockwise around the origin.
|
||||
|
||||
@ -90,7 +89,7 @@ def normalize_mirror(mirrored: Sequence[bool]) -> Tuple[bool, float]:
|
||||
return mirror_x, angle
|
||||
|
||||
|
||||
def remove_duplicate_vertices(vertices: ArrayLike, closed_path: bool = True) -> numpy.ndarray:
|
||||
def remove_duplicate_vertices(vertices: ArrayLike, closed_path: bool = True) -> NDArray[numpy.float64]:
|
||||
"""
|
||||
Given a list of vertices, remove any consecutive duplicates.
|
||||
|
||||
@ -102,13 +101,14 @@ def remove_duplicate_vertices(vertices: ArrayLike, closed_path: bool = True) ->
|
||||
Returns:
|
||||
`vertices` with no consecutive duplicates.
|
||||
"""
|
||||
vertices = numpy.array(vertices)
|
||||
duplicates = (vertices == numpy.roll(vertices, 1, axis=0)).all(axis=1)
|
||||
if not closed_path:
|
||||
duplicates[0] = False
|
||||
return vertices[~duplicates]
|
||||
|
||||
|
||||
def remove_colinear_vertices(vertices: ArrayLike, closed_path: bool = True) -> numpy.ndarray:
|
||||
def remove_colinear_vertices(vertices: ArrayLike, closed_path: bool = True) -> NDArray[numpy.float64]:
|
||||
"""
|
||||
Given a list of vertices, remove any superflous vertices (i.e.
|
||||
those which lie along the line formed by their neighbors)
|
||||
|
Loading…
Reference in New Issue
Block a user