move to dicty layers and targets

This commit is contained in:
jan 2023-04-12 13:56:50 -07:00
commit 9a077ea2df
23 changed files with 694 additions and 638 deletions

View file

@ -9,7 +9,7 @@ from numpy.typing import NDArray, ArrayLike
from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_VERTICES
from ..error import PatternError
from ..repetition import Repetition
from ..utils import is_scalar, layer_t, annotations_t
from ..utils import is_scalar, annotations_t
class Arc(Shape):
@ -24,7 +24,7 @@ class Arc(Shape):
__slots__ = (
'_radii', '_angles', '_width', '_rotation',
# Inherited
'_offset', '_layer', '_repetition', '_annotations',
'_offset', '_repetition', '_annotations',
)
_radii: NDArray[numpy.float64]
@ -156,7 +156,6 @@ class Arc(Shape):
offset: ArrayLike = (0.0, 0.0),
rotation: float = 0,
mirrored: Sequence[bool] = (False, False),
layer: layer_t = 0,
repetition: Repetition | None = None,
annotations: annotations_t | None = None,
raw: bool = False,
@ -172,7 +171,6 @@ class Arc(Shape):
self._rotation = rotation
self._repetition = repetition
self._annotations = annotations if annotations is not None else {}
self._layer = layer
else:
self.radii = radii
self.angles = angles
@ -181,7 +179,6 @@ class Arc(Shape):
self.rotation = rotation
self.repetition = repetition
self.annotations = annotations if annotations is not None else {}
self.layer = layer
[self.mirror(a) for a, do in enumerate(mirrored) if do]
def __deepcopy__(self, memo: dict | None = None) -> 'Arc':
@ -241,7 +238,7 @@ class Arc(Shape):
ys = numpy.hstack((ys1, ys2))
xys = numpy.vstack((xs, ys)).T
poly = Polygon(xys, layer=self.layer, offset=self.offset, rotation=self.rotation)
poly = Polygon(xys, offset=self.offset, rotation=self.rotation)
return [poly]
def get_bounds(self) -> NDArray[numpy.float64]:
@ -352,13 +349,12 @@ class Arc(Shape):
rotation %= 2 * pi
width = self.width
return ((type(self), radii, angles, width / norm_value, self.layer),
return ((type(self), radii, angles, width / norm_value),
(self.offset, scale / norm_value, rotation, False),
lambda: Arc(
radii=radii * norm_value,
angles=angles,
width=width * norm_value,
layer=self.layer,
))
def get_cap_edges(self) -> NDArray[numpy.float64]:
@ -415,4 +411,4 @@ class Arc(Shape):
def __repr__(self) -> str:
angles = f'{numpy.rad2deg(self.angles)}'
rotation = f'{numpy.rad2deg(self.rotation):g}' if self.rotation != 0 else ''
return f'<Arc l{self.layer} o{self.offset} r{self.radii}{angles} w{self.width:g}{rotation}>'
return f'<Arc o{self.offset} r{self.radii}{angles} w{self.width:g}{rotation}>'

View file

@ -7,7 +7,7 @@ from numpy.typing import NDArray, ArrayLike
from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_VERTICES
from ..error import PatternError
from ..repetition import Repetition
from ..utils import is_scalar, layer_t, annotations_t
from ..utils import is_scalar, annotations_t
class Circle(Shape):
@ -17,7 +17,7 @@ class Circle(Shape):
__slots__ = (
'_radius',
# Inherited
'_offset', '_layer', '_repetition', '_annotations',
'_offset', '_repetition', '_annotations',
)
_radius: float
@ -44,7 +44,6 @@ class Circle(Shape):
radius: float,
*,
offset: ArrayLike = (0.0, 0.0),
layer: layer_t = 0,
repetition: Repetition | None = None,
annotations: annotations_t | None = None,
raw: bool = False,
@ -55,13 +54,11 @@ class Circle(Shape):
self._offset = offset
self._repetition = repetition
self._annotations = annotations if annotations is not None else {}
self._layer = layer
else:
self.radius = radius
self.offset = offset
self.repetition = repetition
self.annotations = annotations if annotations is not None else {}
self.layer = layer
def __deepcopy__(self, memo: dict | None = None) -> 'Circle':
memo = {} if memo is None else memo
@ -90,7 +87,7 @@ class Circle(Shape):
ys = numpy.sin(thetas) * self.radius
xys = numpy.vstack((xs, ys)).T
return [Polygon(xys, offset=self.offset, layer=self.layer)]
return [Polygon(xys, offset=self.offset)]
def get_bounds(self) -> NDArray[numpy.float64]:
return numpy.vstack((self.offset - self.radius,
@ -110,9 +107,9 @@ class Circle(Shape):
def normalized_form(self, norm_value) -> normalized_shape_tuple:
rotation = 0.0
magnitude = self.radius / norm_value
return ((type(self), self.layer),
return ((type(self),),
(self.offset, magnitude, rotation, False),
lambda: Circle(radius=norm_value, layer=self.layer))
lambda: Circle(radius=norm_value))
def __repr__(self) -> str:
return f'<Circle l{self.layer} o{self.offset} r{self.radius:g}>'
return f'<Circle o{self.offset} r{self.radius:g}>'

View file

@ -9,7 +9,7 @@ from numpy.typing import ArrayLike, NDArray
from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_VERTICES
from ..error import PatternError
from ..repetition import Repetition
from ..utils import is_scalar, rotation_matrix_2d, layer_t, annotations_t
from ..utils import is_scalar, rotation_matrix_2d, annotations_t
class Ellipse(Shape):
@ -20,7 +20,7 @@ class Ellipse(Shape):
__slots__ = (
'_radii', '_rotation',
# Inherited
'_offset', '_layer', '_repetition', '_annotations',
'_offset', '_repetition', '_annotations',
)
_radii: NDArray[numpy.float64]
@ -91,7 +91,6 @@ class Ellipse(Shape):
offset: ArrayLike = (0.0, 0.0),
rotation: float = 0,
mirrored: Sequence[bool] = (False, False),
layer: layer_t = 0,
repetition: Repetition | None = None,
annotations: annotations_t | None = None,
raw: bool = False,
@ -104,14 +103,12 @@ class Ellipse(Shape):
self._rotation = rotation
self._repetition = repetition
self._annotations = annotations if annotations is not None else {}
self._layer = layer
else:
self.radii = radii
self.offset = offset
self.rotation = rotation
self.repetition = repetition
self.annotations = annotations if annotations is not None else {}
self.layer = layer
[self.mirror(a) for a, do in enumerate(mirrored) if do]
def __deepcopy__(self, memo: dict | None = None) -> 'Ellipse':
@ -152,7 +149,7 @@ class Ellipse(Shape):
ys = r1 * sin_th
xys = numpy.vstack((xs, ys)).T
poly = Polygon(xys, layer=self.layer, offset=self.offset, rotation=self.rotation)
poly = Polygon(xys, offset=self.offset, rotation=self.rotation)
return [poly]
def get_bounds(self) -> NDArray[numpy.float64]:
@ -183,10 +180,10 @@ class Ellipse(Shape):
radii = self.radii[::-1] / self.radius_y
scale = self.radius_y
angle = (self.rotation + pi / 2) % pi
return ((type(self), radii, self.layer),
return ((type(self), radii),
(self.offset, scale / norm_value, angle, False),
lambda: Ellipse(radii=radii * norm_value, layer=self.layer))
lambda: Ellipse(radii=radii * norm_value))
def __repr__(self) -> str:
rotation = f' r{self.rotation*180/pi:g}' if self.rotation != 0 else ''
return f'<Ellipse l{self.layer} o{self.offset} r{self.radii}{rotation}>'
rotation = f' r{numpy.rad2deg(self.rotation):g}' if self.rotation != 0 else ''
return f'<Ellipse o{self.offset} r{self.radii}{rotation}>'

View file

@ -9,7 +9,7 @@ from numpy.typing import NDArray, ArrayLike
from . import Shape, normalized_shape_tuple, Polygon, Circle
from ..error import PatternError
from ..repetition import Repetition
from ..utils import is_scalar, rotation_matrix_2d, layer_t
from ..utils import is_scalar, rotation_matrix_2d
from ..utils import remove_colinear_vertices, remove_duplicate_vertices, annotations_t
@ -31,7 +31,7 @@ class Path(Shape):
__slots__ = (
'_vertices', '_width', '_cap', '_cap_extensions',
# Inherited
'_offset', '_layer', '_repetition', '_annotations',
'_offset', '_repetition', '_annotations',
)
_vertices: NDArray[numpy.float64]
_width: float
@ -154,7 +154,6 @@ class Path(Shape):
offset: ArrayLike = (0.0, 0.0),
rotation: float = 0,
mirrored: Sequence[bool] = (False, False),
layer: layer_t = 0,
repetition: Repetition | None = None,
annotations: annotations_t | None = None,
raw: bool = False,
@ -169,7 +168,6 @@ class Path(Shape):
self._offset = offset
self._repetition = repetition
self._annotations = annotations if annotations is not None else {}
self._layer = layer
self._width = width
self._cap = cap
self._cap_extensions = cap_extensions
@ -178,7 +176,6 @@ class Path(Shape):
self.offset = offset
self.repetition = repetition
self.annotations = annotations if annotations is not None else {}
self.layer = layer
self.width = width
self.cap = cap
self.cap_extensions = cap_extensions
@ -204,7 +201,6 @@ class Path(Shape):
offset: ArrayLike = (0.0, 0.0),
rotation: float = 0,
mirrored: Sequence[bool] = (False, False),
layer: layer_t = 0,
) -> 'Path':
"""
Build a path by specifying the turn angles and travel distances
@ -224,7 +220,6 @@ class Path(Shape):
mirrored: Whether to mirror across the x or y axes. For example,
`mirrored=(True, False)` results in a reflection across the x-axis,
multiplying the path's y-coordinates by -1. Default `(False, False)`
layer: Layer, default `0`
Returns:
The resulting Path object
@ -238,8 +233,7 @@ class Path(Shape):
verts.append(verts[-1] + direction * distance)
return Path(vertices=verts, width=width, cap=cap, cap_extensions=cap_extensions,
offset=offset, rotation=rotation, mirrored=mirrored,
layer=layer)
offset=offset, rotation=rotation, mirrored=mirrored)
def to_polygons(
self,
@ -254,7 +248,7 @@ class Path(Shape):
if self.width == 0:
verts = numpy.vstack((v, v[::-1]))
return [Polygon(offset=self.offset, vertices=verts, layer=self.layer)]
return [Polygon(offset=self.offset, vertices=verts)]
perp = dvdir[:, ::-1] * [[1, -1]] * self.width / 2
@ -305,12 +299,12 @@ class Path(Shape):
o1.append(v[-1] - perp[-1])
verts = numpy.vstack((o0, o1[::-1]))
polys = [Polygon(offset=self.offset, vertices=verts, layer=self.layer)]
polys = [Polygon(offset=self.offset, vertices=verts)]
if self.cap == PathCap.Circle:
#for vert in v: # not sure if every vertex, or just ends?
for vert in [v[0], v[-1]]:
circ = Circle(offset=vert, radius=self.width / 2, layer=self.layer)
circ = Circle(offset=vert, radius=self.width / 2)
polys += circ.to_polygons(num_vertices=num_vertices, max_arclen=max_arclen)
return polys
@ -370,13 +364,12 @@ class Path(Shape):
width0 = self.width / norm_value
return ((type(self), reordered_vertices.data.tobytes(), width0, self.cap, self.layer),
return ((type(self), reordered_vertices.data.tobytes(), width0, self.cap),
(offset, scale / norm_value, rotation, False),
lambda: Path(
reordered_vertices * norm_value,
width=self.width * norm_value,
cap=self.cap,
layer=self.layer,
))
def clean_vertices(self) -> 'Path':
@ -422,4 +415,4 @@ class Path(Shape):
def __repr__(self) -> str:
centroid = self.offset + self.vertices.mean(axis=0)
return f'<Path l{self.layer} centroid {centroid} v{len(self.vertices)} w{self.width} c{self.cap}>'
return f'<Path centroid {centroid} v{len(self.vertices)} w{self.width} c{self.cap}>'

View file

@ -8,7 +8,7 @@ from numpy.typing import NDArray, ArrayLike
from . import Shape, normalized_shape_tuple
from ..error import PatternError
from ..repetition import Repetition
from ..utils import is_scalar, rotation_matrix_2d, layer_t
from ..utils import is_scalar, rotation_matrix_2d
from ..utils import remove_colinear_vertices, remove_duplicate_vertices, annotations_t
@ -22,7 +22,7 @@ class Polygon(Shape):
__slots__ = (
'_vertices',
# Inherited
'_offset', '_layer', '_repetition', '_annotations',
'_offset', '_repetition', '_annotations',
)
_vertices: NDArray[numpy.float64]
@ -82,7 +82,6 @@ class Polygon(Shape):
offset: ArrayLike = (0.0, 0.0),
rotation: float = 0.0,
mirrored: Sequence[bool] = (False, False),
layer: layer_t = 0,
repetition: Repetition | None = None,
annotations: annotations_t | None = None,
raw: bool = False,
@ -94,13 +93,11 @@ class Polygon(Shape):
self._offset = offset
self._repetition = repetition
self._annotations = annotations if annotations is not None else {}
self._layer = layer
else:
self.vertices = vertices
self.offset = offset
self.repetition = repetition
self.annotations = annotations if annotations is not None else {}
self.layer = layer
self.rotate(rotation)
[self.mirror(a) for a, do in enumerate(mirrored) if do]
@ -118,7 +115,6 @@ class Polygon(Shape):
*,
rotation: float = 0.0,
offset: ArrayLike = (0.0, 0.0),
layer: layer_t = 0,
repetition: Repetition | None = None,
) -> 'Polygon':
"""
@ -128,7 +124,6 @@ class Polygon(Shape):
side_length: Length of one side
rotation: Rotation counterclockwise, in radians
offset: Offset, default `(0, 0)`
layer: Layer, default `0`
repetition: `Repetition` object, default `None`
Returns:
@ -139,7 +134,7 @@ class Polygon(Shape):
[+1, +1],
[+1, -1]], dtype=float)
vertices = 0.5 * side_length * norm_square
poly = Polygon(vertices, offset=offset, layer=layer, repetition=repetition)
poly = Polygon(vertices, offset=offset, repetition=repetition)
poly.rotate(rotation)
return poly
@ -150,7 +145,6 @@ class Polygon(Shape):
*,
rotation: float = 0,
offset: ArrayLike = (0.0, 0.0),
layer: layer_t = 0,
repetition: Repetition | None = None,
) -> 'Polygon':
"""
@ -161,7 +155,6 @@ class Polygon(Shape):
ly: Length along y (before rotation)
rotation: Rotation counterclockwise, in radians
offset: Offset, default `(0, 0)`
layer: Layer, default `0`
repetition: `Repetition` object, default `None`
Returns:
@ -171,7 +164,7 @@ class Polygon(Shape):
[-lx, +ly],
[+lx, +ly],
[+lx, -ly]], dtype=float)
poly = Polygon(vertices, offset=offset, layer=layer, repetition=repetition)
poly = Polygon(vertices, offset=offset, repetition=repetition)
poly.rotate(rotation)
return poly
@ -186,7 +179,6 @@ class Polygon(Shape):
yctr: float | None = None,
ymax: float | None = None,
ly: float | None = None,
layer: layer_t = 0,
repetition: Repetition | None = None,
) -> 'Polygon':
"""
@ -204,7 +196,6 @@ class Polygon(Shape):
yctr: Center y coordinate
ymax: Maximum y coordinate
ly: Length along y direction
layer: Layer, default `0`
repetition: `Repetition` object, default `None`
Returns:
@ -270,7 +261,7 @@ class Polygon(Shape):
else:
raise PatternError('Two of ymin, yctr, ymax, ly must be None!')
poly = Polygon.rectangle(lx, ly, offset=(xctr, yctr), layer=layer, repetition=repetition)
poly = Polygon.rectangle(lx, ly, offset=(xctr, yctr), repetition=repetition)
return poly
@staticmethod
@ -281,7 +272,6 @@ class Polygon(Shape):
regular: bool = True,
center: ArrayLike = (0.0, 0.0),
rotation: float = 0.0,
layer: layer_t = 0,
repetition: Repetition | None = None,
) -> 'Polygon':
"""
@ -300,7 +290,6 @@ class Polygon(Shape):
rotation: Rotation counterclockwise, in radians.
`0` results in four axis-aligned sides (the long sides of the
irregular octagon).
layer: Layer, default `0`
repetition: `Repetition` object, default `None`
Returns:
@ -327,7 +316,7 @@ class Polygon(Shape):
side_length = 2 * inner_radius / s
vertices = 0.5 * side_length * norm_oct
poly = Polygon(vertices, offset=center, layer=layer, repetition=repetition)
poly = Polygon(vertices, offset=center, repetition=repetition)
poly.rotate(rotation)
return poly
@ -378,9 +367,9 @@ class Polygon(Shape):
# TODO: normalize mirroring?
return ((type(self), reordered_vertices.data.tobytes(), self.layer),
return ((type(self), reordered_vertices.data.tobytes()),
(offset, scale / norm_value, rotation, False),
lambda: Polygon(reordered_vertices * norm_value, layer=self.layer))
lambda: Polygon(reordered_vertices * norm_value))
def clean_vertices(self) -> 'Polygon':
"""
@ -414,4 +403,4 @@ class Polygon(Shape):
def __repr__(self) -> str:
centroid = self.offset + self.vertices.mean(axis=0)
return f'<Polygon l{self.layer} centroid {centroid} v{len(self.vertices)}>'
return f'<Polygon centroid {centroid} v{len(self.vertices)}>'

View file

@ -5,9 +5,8 @@ import numpy
from numpy.typing import NDArray, ArrayLike
from ..traits import (
Rotatable, Mirrorable, Copyable, Scalable,
PositionableImpl, LayerableImpl,
PivotableImpl, RepeatableImpl, AnnotatableImpl,
Rotatable, Mirrorable, Copyable, Scalable, Bounded,
PositionableImpl, PivotableImpl, RepeatableImpl, AnnotatableImpl,
)
if TYPE_CHECKING:
@ -26,7 +25,7 @@ normalized_shape_tuple = tuple[
DEFAULT_POLY_NUM_VERTICES = 24
class Shape(PositionableImpl, LayerableImpl, Rotatable, Mirrorable, Copyable, Scalable,
class Shape(PositionableImpl, Rotatable, Mirrorable, Copyable, Scalable, Bounded,
PivotableImpl, RepeatableImpl, AnnotatableImpl, metaclass=ABCMeta):
"""
Class specifying functions common to all shapes.
@ -194,10 +193,7 @@ class Shape(PositionableImpl, LayerableImpl, Rotatable, Mirrorable, Copyable, Sc
vertex_lists.append(vlist)
polygon_contours.append(numpy.vstack(vertex_lists))
manhattan_polygons = [
Polygon(vertices=contour, layer=self.layer)
for contour in polygon_contours
]
manhattan_polygons = [Polygon(vertices=contour) for contour in polygon_contours]
return manhattan_polygons
@ -292,9 +288,6 @@ class Shape(PositionableImpl, LayerableImpl, Rotatable, Mirrorable, Copyable, Sc
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,
layer=self.layer,
))
manhattan_polygons.append(Polygon(vertices=vertices))
return manhattan_polygons

View file

@ -9,7 +9,7 @@ from . import Shape, Polygon, normalized_shape_tuple
from ..error import PatternError
from ..repetition import Repetition
from ..traits import RotatableImpl
from ..utils import is_scalar, get_bit, normalize_mirror, layer_t
from ..utils import is_scalar, get_bit, normalize_mirror
from ..utils import annotations_t
# Loaded on use:
@ -25,7 +25,7 @@ class Text(RotatableImpl, Shape):
__slots__ = (
'_string', '_height', '_mirrored', 'font_path',
# Inherited
'_offset', '_layer', '_repetition', '_annotations', '_rotation',
'_offset', '_repetition', '_annotations', '_rotation',
)
_string: str
@ -73,7 +73,6 @@ class Text(RotatableImpl, Shape):
offset: ArrayLike = (0.0, 0.0),
rotation: float = 0.0,
mirrored: ArrayLike = (False, False),
layer: layer_t = 0,
repetition: Repetition | None = None,
annotations: annotations_t | None = None,
raw: bool = False,
@ -82,7 +81,6 @@ class Text(RotatableImpl, Shape):
assert isinstance(offset, numpy.ndarray)
assert isinstance(mirrored, numpy.ndarray)
self._offset = offset
self._layer = layer
self._string = string
self._height = height
self._rotation = rotation
@ -91,7 +89,6 @@ class Text(RotatableImpl, Shape):
self._annotations = annotations if annotations is not None else {}
else:
self.offset = offset
self.layer = layer
self.string = string
self.height = height
self.rotation = rotation
@ -120,7 +117,7 @@ class Text(RotatableImpl, Shape):
# Move these polygons to the right of the previous letter
for xys in raw_polys:
poly = Polygon(xys, layer=self.layer)
poly = Polygon(xys)
poly.mirror2d(self.mirrored)
poly.scale_by(self.height)
poly.offset = self.offset + [total_advance, 0]
@ -144,7 +141,7 @@ class Text(RotatableImpl, Shape):
mirror_x, rotation = normalize_mirror(self.mirrored)
rotation += self.rotation
rotation %= 2 * pi
return ((type(self), self.string, self.font_path, self.layer),
return ((type(self), self.string, self.font_path),
(self.offset, self.height / norm_value, rotation, mirror_x),
lambda: Text(
string=self.string,
@ -152,7 +149,6 @@ class Text(RotatableImpl, Shape):
font_path=self.font_path,
rotation=rotation,
mirrored=(mirror_x, False),
layer=self.layer,
))
def get_bounds(self) -> NDArray[numpy.float64]:
@ -256,6 +252,6 @@ def get_char_as_polygons(
return polygons, advance
def __repr__(self) -> str:
rotation = f'{self.rotation*180/pi:g}' if self.rotation != 0 else ''
rotation = f'{numpy.rad2deg(self.rotation):g}' if self.rotation != 0 else ''
mirrored = ' m{:d}{:d}'.format(*self.mirrored) if self.mirrored.any() else ''
return f'<TextShape "{self.string}" l{self.layer} o{self.offset} h{self.height:g}{rotation}{mirrored}>'
return f'<TextShape "{self.string}" o{self.offset} h{self.height:g}{rotation}{mirrored}>'