Use ArrayLike and NDArray wherever possible. Some type fixes and some related corner cases
This commit is contained in:
parent
89f327ba37
commit
a4fe3d9e2e
20 changed files with 291 additions and 224 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue