move to dicty layers and targets
This commit is contained in:
parent
6b240de268
commit
9a077ea2df
23 changed files with 694 additions and 638 deletions
|
|
@ -219,7 +219,7 @@ def _read_block(block) -> tuple[str, Pattern]:
|
|||
|
||||
if points.shape[1] == 2:
|
||||
raise PatternError('Invalid or unimplemented polygon?')
|
||||
#shape = Polygon(layer=layer)
|
||||
#shape = Polygon()
|
||||
elif points.shape[1] > 2:
|
||||
if (points[0, 2] != points[:, 2]).any():
|
||||
raise PatternError('PolyLine has non-constant width (not yet representable in masque!)')
|
||||
|
|
@ -232,11 +232,11 @@ def _read_block(block) -> tuple[str, Pattern]:
|
|||
|
||||
shape: Path | Polygon
|
||||
if width == 0 and len(points) > 2 and numpy.array_equal(points[0], points[-1]):
|
||||
shape = Polygon(layer=layer, vertices=points[:-1, :2])
|
||||
shape = Polygon(vertices=points[:-1, :2])
|
||||
else:
|
||||
shape = Path(layer=layer, width=width, vertices=points[:, :2])
|
||||
shape = Path(width=width, vertices=points[:, :2])
|
||||
|
||||
pat.shapes.append(shape)
|
||||
pat.shapes[layer].append(shape)
|
||||
|
||||
elif eltype in ('TEXT',):
|
||||
args = dict(
|
||||
|
|
@ -248,9 +248,9 @@ def _read_block(block) -> tuple[str, Pattern]:
|
|||
# if height != 0:
|
||||
# logger.warning('Interpreting DXF TEXT as a label despite nonzero height. '
|
||||
# 'This could be changed in the future by setting a font path in the masque DXF code.')
|
||||
pat.labels.append(Label(string=string, **args))
|
||||
pat.label(string=string, **args)
|
||||
# else:
|
||||
# pat.shapes.append(Text(string=string, height=height, font_path=????))
|
||||
# pat.shapes[args['layer']].append(Text(string=string, height=height, font_path=????))
|
||||
elif eltype in ('INSERT',):
|
||||
attr = element.dxfattribs()
|
||||
xscale = attr.get('xscale', 1)
|
||||
|
|
@ -286,13 +286,9 @@ def _read_block(block) -> tuple[str, Pattern]:
|
|||
|
||||
def _mrefs_to_drefs(
|
||||
block: ezdxf.layouts.BlockLayout | ezdxf.layouts.Modelspace,
|
||||
refs: list[Ref],
|
||||
refs: dict[str | None, list[Ref]],
|
||||
) -> None:
|
||||
for ref in refs:
|
||||
if ref.target is None:
|
||||
continue
|
||||
encoded_name = ref.target
|
||||
|
||||
def mk_blockref(encoded_name: str, ref: Ref) -> None:
|
||||
rotation = numpy.rad2deg(ref.rotation) % 360
|
||||
attribs = dict(
|
||||
xscale=ref.scale * (-1 if ref.mirrored[1] else 1),
|
||||
|
|
@ -330,36 +326,47 @@ def _mrefs_to_drefs(
|
|||
for dd in rep.displacements:
|
||||
block.add_blockref(encoded_name, ref.offset + dd, dxfattribs=attribs)
|
||||
|
||||
for target, rseq in refs.items():
|
||||
if target is None:
|
||||
continue
|
||||
for ref in rseq:
|
||||
mk_blockref(target, ref)
|
||||
|
||||
|
||||
def _shapes_to_elements(
|
||||
block: ezdxf.layouts.BlockLayout | ezdxf.layouts.Modelspace,
|
||||
shapes: list[Shape],
|
||||
shapes: dict[layer_t, list[Shape]],
|
||||
polygonize_paths: bool = False,
|
||||
) -> None:
|
||||
# Add `LWPolyline`s for each shape.
|
||||
# Could set do paths with width setting, but need to consider endcaps.
|
||||
for shape in shapes:
|
||||
if shape.repetition is not None:
|
||||
raise PatternError(
|
||||
'Shape repetitions are not supported by DXF.'
|
||||
' Please call library.wrap_repeated_shapes() before writing to file.'
|
||||
)
|
||||
for layer, sseq in shapes.items():
|
||||
attribs = dict(layer=_mlayer2dxf(layer))
|
||||
for shape in sseq:
|
||||
if shape.repetition is not None:
|
||||
raise PatternError(
|
||||
'Shape repetitions are not supported by DXF.'
|
||||
' Please call library.wrap_repeated_shapes() before writing to file.'
|
||||
)
|
||||
|
||||
attribs = dict(layer=_mlayer2dxf(shape.layer))
|
||||
for polygon in shape.to_polygons():
|
||||
xy_open = polygon.vertices + polygon.offset
|
||||
xy_closed = numpy.vstack((xy_open, xy_open[0, :]))
|
||||
block.add_lwpolyline(xy_closed, dxfattribs=attribs)
|
||||
for polygon in shape.to_polygons():
|
||||
xy_open = polygon.vertices + polygon.offset
|
||||
xy_closed = numpy.vstack((xy_open, xy_open[0, :]))
|
||||
block.add_lwpolyline(xy_closed, dxfattribs=attribs)
|
||||
|
||||
|
||||
def _labels_to_texts(
|
||||
block: ezdxf.layouts.BlockLayout | ezdxf.layouts.Modelspace,
|
||||
labels: list[Label],
|
||||
labels: dict[layer_t, list[Label]],
|
||||
) -> None:
|
||||
for label in labels:
|
||||
attribs = dict(layer=_mlayer2dxf(label.layer))
|
||||
xy = label.offset
|
||||
block.add_text(label.string, dxfattribs=attribs).set_placement(xy, align=TextEntityAlignment.BOTTOM_LEFT)
|
||||
for layer, lseq in labels.items():
|
||||
attribs = dict(layer=_mlayer2dxf(layer))
|
||||
for label in lseq:
|
||||
xy = label.offset
|
||||
block.add_text(
|
||||
label.string,
|
||||
dxfattribs=attribs
|
||||
).set_placement(xy, align=TextEntityAlignment.BOTTOM_LEFT)
|
||||
|
||||
|
||||
def _mlayer2dxf(layer: layer_t) -> str:
|
||||
|
|
|
|||
|
|
@ -253,21 +253,21 @@ def read_elements(
|
|||
elements = klamath.library.read_elements(stream)
|
||||
for element in elements:
|
||||
if isinstance(element, klamath.elements.Boundary):
|
||||
poly = _boundary_to_polygon(element, raw_mode)
|
||||
pat.shapes.append(poly)
|
||||
layer, poly = _boundary_to_polygon(element, raw_mode)
|
||||
pat.shapes[layer].append(poly)
|
||||
elif isinstance(element, klamath.elements.Path):
|
||||
path = _gpath_to_mpath(element, raw_mode)
|
||||
pat.shapes.append(path)
|
||||
layer, path = _gpath_to_mpath(element, raw_mode)
|
||||
pat.shapes[layer].append(path)
|
||||
elif isinstance(element, klamath.elements.Text):
|
||||
label = Label(
|
||||
offset=element.xy.astype(float),
|
||||
pat.label(
|
||||
layer=element.layer,
|
||||
offset=element.xy.astype(float),
|
||||
string=element.string.decode('ASCII'),
|
||||
annotations=_properties_to_annotations(element.properties),
|
||||
)
|
||||
pat.labels.append(label)
|
||||
elif isinstance(element, klamath.elements.Reference):
|
||||
pat.refs.append(_gref_to_mref(element))
|
||||
target, ref = _gref_to_mref(element)
|
||||
pat.refs[target].append(ref)
|
||||
return pat
|
||||
|
||||
|
||||
|
|
@ -287,7 +287,7 @@ def _mlayer2gds(mlayer: layer_t) -> tuple[int, int]:
|
|||
return layer, data_type
|
||||
|
||||
|
||||
def _gref_to_mref(ref: klamath.library.Reference) -> Ref:
|
||||
def _gref_to_mref(ref: klamath.library.Reference) -> tuple[str, Ref]:
|
||||
"""
|
||||
Helper function to create a Ref from an SREF or AREF. Sets ref.target to struct_name.
|
||||
"""
|
||||
|
|
@ -301,8 +301,8 @@ def _gref_to_mref(ref: klamath.library.Reference) -> Ref:
|
|||
repetition = Grid(a_vector=a_vector, b_vector=b_vector,
|
||||
a_count=a_count, b_count=b_count)
|
||||
|
||||
target = ref.struct_name.decode('ASCII')
|
||||
mref = Ref(
|
||||
target=ref.struct_name.decode('ASCII'),
|
||||
offset=offset,
|
||||
rotation=numpy.deg2rad(ref.angle_deg),
|
||||
scale=ref.mag,
|
||||
|
|
@ -310,10 +310,10 @@ def _gref_to_mref(ref: klamath.library.Reference) -> Ref:
|
|||
annotations=_properties_to_annotations(ref.properties),
|
||||
repetition=repetition,
|
||||
)
|
||||
return mref
|
||||
return target, mref
|
||||
|
||||
|
||||
def _gpath_to_mpath(gpath: klamath.library.Path, raw_mode: bool) -> Path:
|
||||
def _gpath_to_mpath(gpath: klamath.library.Path, raw_mode: bool) -> tuple[layer_t, Path]:
|
||||
if gpath.path_type in path_cap_map:
|
||||
cap = path_cap_map[gpath.path_type]
|
||||
else:
|
||||
|
|
@ -321,7 +321,6 @@ def _gpath_to_mpath(gpath: klamath.library.Path, raw_mode: bool) -> Path:
|
|||
|
||||
mpath = Path(
|
||||
vertices=gpath.xy.astype(float),
|
||||
layer=gpath.layer,
|
||||
width=gpath.width,
|
||||
cap=cap,
|
||||
offset=numpy.zeros(2),
|
||||
|
|
@ -330,74 +329,73 @@ def _gpath_to_mpath(gpath: klamath.library.Path, raw_mode: bool) -> Path:
|
|||
)
|
||||
if cap == Path.Cap.SquareCustom:
|
||||
mpath.cap_extensions = gpath.extension
|
||||
return mpath
|
||||
return gpath.layer, mpath
|
||||
|
||||
|
||||
def _boundary_to_polygon(boundary: klamath.library.Boundary, raw_mode: bool) -> Polygon:
|
||||
return Polygon(
|
||||
def _boundary_to_polygon(boundary: klamath.library.Boundary, raw_mode: bool) -> tuple[layer_t, Polygon]:
|
||||
return boundary.layer, Polygon(
|
||||
vertices=boundary.xy[:-1].astype(float),
|
||||
layer=boundary.layer,
|
||||
offset=numpy.zeros(2),
|
||||
annotations=_properties_to_annotations(boundary.properties),
|
||||
raw=raw_mode,
|
||||
)
|
||||
|
||||
|
||||
def _mrefs_to_grefs(refs: list[Ref]) -> list[klamath.library.Reference]:
|
||||
def _mrefs_to_grefs(refs: dict[str | None, list[Ref]]) -> list[klamath.library.Reference]:
|
||||
grefs = []
|
||||
for ref in refs:
|
||||
if ref.target is None:
|
||||
for target, rseq in refs.items():
|
||||
if target is None:
|
||||
continue
|
||||
encoded_name = ref.target.encode('ASCII')
|
||||
encoded_name = target.encode('ASCII')
|
||||
for ref in rseq:
|
||||
# Note: GDS mirrors first and rotates second
|
||||
mirror_across_x, extra_angle = normalize_mirror(ref.mirrored)
|
||||
rep = ref.repetition
|
||||
angle_deg = numpy.rad2deg(ref.rotation + extra_angle) % 360
|
||||
properties = _annotations_to_properties(ref.annotations, 512)
|
||||
|
||||
# Note: GDS mirrors first and rotates second
|
||||
mirror_across_x, extra_angle = normalize_mirror(ref.mirrored)
|
||||
rep = ref.repetition
|
||||
angle_deg = numpy.rad2deg(ref.rotation + extra_angle) % 360
|
||||
properties = _annotations_to_properties(ref.annotations, 512)
|
||||
|
||||
if isinstance(rep, Grid):
|
||||
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 = numpy.array(ref.offset) + numpy.array([
|
||||
[0.0, 0.0],
|
||||
rep.a_vector * rep.a_count,
|
||||
b_vector * b_count,
|
||||
])
|
||||
aref = klamath.library.Reference(
|
||||
struct_name=encoded_name,
|
||||
xy=rint_cast(xy),
|
||||
colrow=(numpy.rint(rep.a_count), numpy.rint(rep.b_count)),
|
||||
angle_deg=angle_deg,
|
||||
invert_y=mirror_across_x,
|
||||
mag=ref.scale,
|
||||
properties=properties,
|
||||
)
|
||||
grefs.append(aref)
|
||||
elif rep is None:
|
||||
sref = klamath.library.Reference(
|
||||
struct_name=encoded_name,
|
||||
xy=rint_cast([ref.offset]),
|
||||
colrow=None,
|
||||
angle_deg=angle_deg,
|
||||
invert_y=mirror_across_x,
|
||||
mag=ref.scale,
|
||||
properties=properties,
|
||||
)
|
||||
grefs.append(sref)
|
||||
else:
|
||||
new_srefs = [
|
||||
klamath.library.Reference(
|
||||
if isinstance(rep, Grid):
|
||||
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 = numpy.array(ref.offset) + numpy.array([
|
||||
[0.0, 0.0],
|
||||
rep.a_vector * rep.a_count,
|
||||
b_vector * b_count,
|
||||
])
|
||||
aref = klamath.library.Reference(
|
||||
struct_name=encoded_name,
|
||||
xy=rint_cast([ref.offset + dd]),
|
||||
xy=rint_cast(xy),
|
||||
colrow=(numpy.rint(rep.a_count), numpy.rint(rep.b_count)),
|
||||
angle_deg=angle_deg,
|
||||
invert_y=mirror_across_x,
|
||||
mag=ref.scale,
|
||||
properties=properties,
|
||||
)
|
||||
grefs.append(aref)
|
||||
elif rep is None:
|
||||
sref = klamath.library.Reference(
|
||||
struct_name=encoded_name,
|
||||
xy=rint_cast([ref.offset]),
|
||||
colrow=None,
|
||||
angle_deg=angle_deg,
|
||||
invert_y=mirror_across_x,
|
||||
mag=ref.scale,
|
||||
properties=properties,
|
||||
)
|
||||
for dd in rep.displacements]
|
||||
grefs += new_srefs
|
||||
grefs.append(sref)
|
||||
else:
|
||||
new_srefs = [
|
||||
klamath.library.Reference(
|
||||
struct_name=encoded_name,
|
||||
xy=rint_cast([ref.offset + dd]),
|
||||
colrow=None,
|
||||
angle_deg=angle_deg,
|
||||
invert_y=mirror_across_x,
|
||||
mag=ref.scale,
|
||||
properties=properties,
|
||||
)
|
||||
for dd in rep.displacements]
|
||||
grefs += new_srefs
|
||||
return grefs
|
||||
|
||||
|
||||
|
|
@ -428,51 +426,41 @@ def _annotations_to_properties(annotations: annotations_t, max_len: int = 126) -
|
|||
|
||||
|
||||
def _shapes_to_elements(
|
||||
shapes: list[Shape],
|
||||
shapes: dict[layer_t, list[Shape]],
|
||||
polygonize_paths: bool = False,
|
||||
) -> list[klamath.elements.Element]:
|
||||
elements: list[klamath.elements.Element] = []
|
||||
# Add a Boundary element for each shape, and Path elements if necessary
|
||||
for shape in shapes:
|
||||
if shape.repetition is not None:
|
||||
raise PatternError('Shape repetitions are not supported by GDS.'
|
||||
' Please call library.wrap_repeated_shapes() before writing to file.')
|
||||
for mlayer, sseq in shapes.items():
|
||||
layer, data_type = _mlayer2gds(mlayer)
|
||||
for shape in sseq:
|
||||
if shape.repetition is not None:
|
||||
raise PatternError('Shape repetitions are not supported by GDS.'
|
||||
' Please call library.wrap_repeated_shapes() before writing to file.')
|
||||
|
||||
layer, data_type = _mlayer2gds(shape.layer)
|
||||
properties = _annotations_to_properties(shape.annotations, 128)
|
||||
if isinstance(shape, Path) and not polygonize_paths:
|
||||
xy = rint_cast(shape.vertices + shape.offset)
|
||||
width = rint_cast(shape.width)
|
||||
path_type = next(k for k, v in path_cap_map.items() if v == shape.cap) # reverse lookup
|
||||
properties = _annotations_to_properties(shape.annotations, 128)
|
||||
if isinstance(shape, Path) and not polygonize_paths:
|
||||
xy = rint_cast(shape.vertices + shape.offset)
|
||||
width = rint_cast(shape.width)
|
||||
path_type = next(k for k, v in path_cap_map.items() if v == shape.cap) # reverse lookup
|
||||
|
||||
extension: tuple[int, int]
|
||||
if shape.cap == Path.Cap.SquareCustom and shape.cap_extensions is not None:
|
||||
extension = tuple(shape.cap_extensions) # type: ignore
|
||||
else:
|
||||
extension = (0, 0)
|
||||
extension: tuple[int, int]
|
||||
if shape.cap == Path.Cap.SquareCustom and shape.cap_extensions is not None:
|
||||
extension = tuple(shape.cap_extensions) # type: ignore
|
||||
else:
|
||||
extension = (0, 0)
|
||||
|
||||
path = klamath.elements.Path(
|
||||
layer=(layer, data_type),
|
||||
xy=xy,
|
||||
path_type=path_type,
|
||||
width=int(width),
|
||||
extension=extension,
|
||||
properties=properties,
|
||||
)
|
||||
elements.append(path)
|
||||
elif isinstance(shape, Polygon):
|
||||
polygon = shape
|
||||
xy_closed = numpy.empty((polygon.vertices.shape[0] + 1, 2), dtype=numpy.int32)
|
||||
numpy.rint(polygon.vertices + polygon.offset, out=xy_closed[:-1], casting='unsafe')
|
||||
xy_closed[-1] = xy_closed[0]
|
||||
boundary = klamath.elements.Boundary(
|
||||
layer=(layer, data_type),
|
||||
xy=xy_closed,
|
||||
properties=properties,
|
||||
)
|
||||
elements.append(boundary)
|
||||
else:
|
||||
for polygon in shape.to_polygons():
|
||||
path = klamath.elements.Path(
|
||||
layer=(layer, data_type),
|
||||
xy=xy,
|
||||
path_type=path_type,
|
||||
width=int(width),
|
||||
extension=extension,
|
||||
properties=properties,
|
||||
)
|
||||
elements.append(path)
|
||||
elif isinstance(shape, Polygon):
|
||||
polygon = shape
|
||||
xy_closed = numpy.empty((polygon.vertices.shape[0] + 1, 2), dtype=numpy.int32)
|
||||
numpy.rint(polygon.vertices + polygon.offset, out=xy_closed[:-1], casting='unsafe')
|
||||
xy_closed[-1] = xy_closed[0]
|
||||
|
|
@ -482,28 +470,40 @@ def _shapes_to_elements(
|
|||
properties=properties,
|
||||
)
|
||||
elements.append(boundary)
|
||||
else:
|
||||
for polygon in shape.to_polygons():
|
||||
xy_closed = numpy.empty((polygon.vertices.shape[0] + 1, 2), dtype=numpy.int32)
|
||||
numpy.rint(polygon.vertices + polygon.offset, out=xy_closed[:-1], casting='unsafe')
|
||||
xy_closed[-1] = xy_closed[0]
|
||||
boundary = klamath.elements.Boundary(
|
||||
layer=(layer, data_type),
|
||||
xy=xy_closed,
|
||||
properties=properties,
|
||||
)
|
||||
elements.append(boundary)
|
||||
return elements
|
||||
|
||||
|
||||
def _labels_to_texts(labels: list[Label]) -> list[klamath.elements.Text]:
|
||||
def _labels_to_texts(labels: dict[layer_t, list[Label]]) -> list[klamath.elements.Text]:
|
||||
texts = []
|
||||
for label in labels:
|
||||
properties = _annotations_to_properties(label.annotations, 128)
|
||||
layer, text_type = _mlayer2gds(label.layer)
|
||||
xy = rint_cast([label.offset])
|
||||
text = klamath.elements.Text(
|
||||
layer=(layer, text_type),
|
||||
xy=xy,
|
||||
string=label.string.encode('ASCII'),
|
||||
properties=properties,
|
||||
presentation=0, # TODO maybe set some of these?
|
||||
angle_deg=0,
|
||||
invert_y=False,
|
||||
width=0,
|
||||
path_type=0,
|
||||
mag=1,
|
||||
)
|
||||
texts.append(text)
|
||||
for mlayer, lseq in labels.items():
|
||||
layer, text_type = _mlayer2gds(mlayer)
|
||||
for label in lseq:
|
||||
properties = _annotations_to_properties(label.annotations, 128)
|
||||
xy = rint_cast([label.offset])
|
||||
text = klamath.elements.Text(
|
||||
layer=(layer, text_type),
|
||||
xy=xy,
|
||||
string=label.string.encode('ASCII'),
|
||||
properties=properties,
|
||||
presentation=0, # TODO maybe set some of these?
|
||||
angle_deg=0,
|
||||
invert_y=False,
|
||||
width=0,
|
||||
path_type=0,
|
||||
mag=1,
|
||||
)
|
||||
texts.append(text)
|
||||
return texts
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ from fatamorgana.basic import PathExtensionScheme, AString, NString, PropStringR
|
|||
from .utils import is_gzipped, tmpfile
|
||||
from .. import Pattern, Ref, PatternError, LibraryError, Label, Shape
|
||||
from ..library import Library, ILibrary
|
||||
from ..shapes import Polygon, Path, Circle
|
||||
from ..shapes import Path, Circle
|
||||
from ..repetition import Grid, Arbitrary, Repetition
|
||||
from ..utils import layer_t, normalize_mirror, annotations_t
|
||||
|
||||
|
|
@ -284,16 +284,13 @@ def read(
|
|||
vertices = numpy.cumsum(numpy.vstack(((0, 0), element.get_point_list()[:-1])), axis=0)
|
||||
|
||||
annotations = properties_to_annotations(element.properties, lib.propnames, lib.propstrings)
|
||||
poly = Polygon(
|
||||
pat.polygon(
|
||||
vertices=vertices,
|
||||
layer=element.get_layer_tuple(),
|
||||
offset=element.get_xy(),
|
||||
annotations=annotations,
|
||||
repetition=repetition,
|
||||
)
|
||||
|
||||
pat.shapes.append(poly)
|
||||
|
||||
elif isinstance(element, fatrec.Path):
|
||||
vertices = numpy.cumsum(numpy.vstack(((0, 0), element.get_point_list())), axis=0)
|
||||
|
||||
|
|
@ -311,7 +308,7 @@ def read(
|
|||
))
|
||||
|
||||
annotations = properties_to_annotations(element.properties, lib.propnames, lib.propstrings)
|
||||
path = Path(
|
||||
pat.path(
|
||||
vertices=vertices,
|
||||
layer=element.get_layer_tuple(),
|
||||
offset=element.get_xy(),
|
||||
|
|
@ -322,20 +319,17 @@ def read(
|
|||
**path_args,
|
||||
)
|
||||
|
||||
pat.shapes.append(path)
|
||||
|
||||
elif isinstance(element, fatrec.Rectangle):
|
||||
width = element.get_width()
|
||||
height = element.get_height()
|
||||
annotations = properties_to_annotations(element.properties, lib.propnames, lib.propstrings)
|
||||
rect = Polygon(
|
||||
pat.polygon(
|
||||
layer=element.get_layer_tuple(),
|
||||
offset=element.get_xy(),
|
||||
repetition=repetition,
|
||||
vertices=numpy.array(((0, 0), (1, 0), (1, 1), (0, 1))) * (width, height),
|
||||
annotations=annotations,
|
||||
)
|
||||
pat.shapes.append(rect)
|
||||
|
||||
elif isinstance(element, fatrec.Trapezoid):
|
||||
vertices = numpy.array(((0, 0), (1, 0), (1, 1), (0, 1))) * (element.get_width(), element.get_height())
|
||||
|
|
@ -363,14 +357,13 @@ def read(
|
|||
vertices[2, 0] -= b
|
||||
|
||||
annotations = properties_to_annotations(element.properties, lib.propnames, lib.propstrings)
|
||||
trapz = Polygon(
|
||||
pat.polygon(
|
||||
layer=element.get_layer_tuple(),
|
||||
offset=element.get_xy(),
|
||||
repetition=repetition,
|
||||
vertices=vertices,
|
||||
annotations=annotations,
|
||||
)
|
||||
pat.shapes.append(trapz)
|
||||
|
||||
elif isinstance(element, fatrec.CTrapezoid):
|
||||
cttype = element.get_ctrapezoid_type()
|
||||
|
|
@ -419,25 +412,24 @@ def read(
|
|||
vertices[0, 1] += width
|
||||
|
||||
annotations = properties_to_annotations(element.properties, lib.propnames, lib.propstrings)
|
||||
ctrapz = Polygon(
|
||||
pat.polygon(
|
||||
layer=element.get_layer_tuple(),
|
||||
offset=element.get_xy(),
|
||||
repetition=repetition,
|
||||
vertices=vertices,
|
||||
annotations=annotations,
|
||||
)
|
||||
pat.shapes.append(ctrapz)
|
||||
|
||||
elif isinstance(element, fatrec.Circle):
|
||||
annotations = properties_to_annotations(element.properties, lib.propnames, lib.propstrings)
|
||||
layer = element.get_layer_tuple()
|
||||
circle = Circle(
|
||||
layer=element.get_layer_tuple(),
|
||||
offset=element.get_xy(),
|
||||
repetition=repetition,
|
||||
annotations=annotations,
|
||||
radius=float(element.get_radius()),
|
||||
)
|
||||
pat.shapes.append(circle)
|
||||
pat.shapes[layer].append(circle)
|
||||
|
||||
elif isinstance(element, fatrec.Text):
|
||||
annotations = properties_to_annotations(element.properties, lib.propnames, lib.propstrings)
|
||||
|
|
@ -446,21 +438,21 @@ def read(
|
|||
string = lib.textstrings[str_or_ref].string
|
||||
else:
|
||||
string = str_or_ref.string
|
||||
label = Label(
|
||||
pat.label(
|
||||
layer=element.get_layer_tuple(),
|
||||
offset=element.get_xy(),
|
||||
repetition=repetition,
|
||||
annotations=annotations,
|
||||
string=string,
|
||||
)
|
||||
pat.labels.append(label)
|
||||
|
||||
else:
|
||||
logger.warning(f'Skipping record {element} (unimplemented)')
|
||||
continue
|
||||
|
||||
for placement in cell.placements:
|
||||
pat.refs.append(_placement_to_ref(placement, lib))
|
||||
target, ref = _placement_to_ref(placement, lib)
|
||||
pat.refs[target].append(ref)
|
||||
|
||||
mlib[cell_name] = pat
|
||||
|
||||
|
|
@ -484,9 +476,9 @@ def _mlayer2oas(mlayer: layer_t) -> tuple[int, int]:
|
|||
return layer, data_type
|
||||
|
||||
|
||||
def _placement_to_ref(placement: fatrec.Placement, lib: fatamorgana.OasisLayout) -> Ref:
|
||||
def _placement_to_ref(placement: fatrec.Placement, lib: fatamorgana.OasisLayout) -> tuple[int | str, Ref]:
|
||||
"""
|
||||
Helper function to create a Ref from a placment. Sets ref.target to the placement name.
|
||||
Helper function to create a Ref from a placment. Also returns the placement name (or id).
|
||||
"""
|
||||
assert not isinstance(placement.repetition, fatamorgana.ReuseRepetition)
|
||||
xy = numpy.array((placement.x, placement.y))
|
||||
|
|
@ -501,7 +493,6 @@ def _placement_to_ref(placement: fatrec.Placement, lib: fatamorgana.OasisLayout)
|
|||
else:
|
||||
rotation = numpy.deg2rad(float(placement.angle))
|
||||
ref = Ref(
|
||||
target=name,
|
||||
offset=xy,
|
||||
mirrored=(placement.flip, False),
|
||||
rotation=rotation,
|
||||
|
|
@ -509,116 +500,118 @@ def _placement_to_ref(placement: fatrec.Placement, lib: fatamorgana.OasisLayout)
|
|||
repetition=repetition_fata2masq(placement.repetition),
|
||||
annotations=annotations,
|
||||
)
|
||||
return ref
|
||||
return name, ref
|
||||
|
||||
|
||||
def _refs_to_placements(
|
||||
refs: list[Ref],
|
||||
refs: dict[str | None, list[Ref]],
|
||||
) -> list[fatrec.Placement]:
|
||||
placements = []
|
||||
for ref in refs:
|
||||
if ref.target is None:
|
||||
for target, rseq in refs.items():
|
||||
if target is None:
|
||||
continue
|
||||
for ref in rseq:
|
||||
# Note: OASIS mirrors first and rotates second
|
||||
mirror_across_x, extra_angle = normalize_mirror(ref.mirrored)
|
||||
frep, rep_offset = repetition_masq2fata(ref.repetition)
|
||||
|
||||
# Note: OASIS mirrors first and rotates second
|
||||
mirror_across_x, extra_angle = normalize_mirror(ref.mirrored)
|
||||
frep, rep_offset = repetition_masq2fata(ref.repetition)
|
||||
offset = rint_cast(ref.offset + rep_offset)
|
||||
angle = numpy.rad2deg(ref.rotation + extra_angle) % 360
|
||||
placement = fatrec.Placement(
|
||||
name=target,
|
||||
flip=mirror_across_x,
|
||||
angle=angle,
|
||||
magnification=ref.scale,
|
||||
properties=annotations_to_properties(ref.annotations),
|
||||
x=offset[0],
|
||||
y=offset[1],
|
||||
repetition=frep,
|
||||
)
|
||||
|
||||
offset = rint_cast(ref.offset + rep_offset)
|
||||
angle = numpy.rad2deg(ref.rotation + extra_angle) % 360
|
||||
placement = fatrec.Placement(
|
||||
name=ref.target,
|
||||
flip=mirror_across_x,
|
||||
angle=angle,
|
||||
magnification=ref.scale,
|
||||
properties=annotations_to_properties(ref.annotations),
|
||||
x=offset[0],
|
||||
y=offset[1],
|
||||
repetition=frep,
|
||||
)
|
||||
|
||||
placements.append(placement)
|
||||
placements.append(placement)
|
||||
return placements
|
||||
|
||||
|
||||
def _shapes_to_elements(
|
||||
shapes: list[Shape],
|
||||
shapes: dict[layer_t, list[Shape]],
|
||||
layer2oas: Callable[[layer_t], tuple[int, int]],
|
||||
) -> list[fatrec.Polygon | fatrec.Path | fatrec.Circle]:
|
||||
# Add a Polygon record for each shape, and Path elements if necessary
|
||||
elements: list[fatrec.Polygon | fatrec.Path | fatrec.Circle] = []
|
||||
for shape in shapes:
|
||||
layer, datatype = layer2oas(shape.layer)
|
||||
repetition, rep_offset = repetition_masq2fata(shape.repetition)
|
||||
properties = annotations_to_properties(shape.annotations)
|
||||
if isinstance(shape, Circle):
|
||||
offset = rint_cast(shape.offset + rep_offset)
|
||||
radius = rint_cast(shape.radius)
|
||||
circle = fatrec.Circle(
|
||||
layer=layer,
|
||||
datatype=datatype,
|
||||
radius=cast(int, radius),
|
||||
x=offset[0],
|
||||
y=offset[1],
|
||||
properties=properties,
|
||||
repetition=repetition,
|
||||
)
|
||||
elements.append(circle)
|
||||
elif isinstance(shape, Path):
|
||||
xy = rint_cast(shape.offset + shape.vertices[0] + rep_offset)
|
||||
deltas = rint_cast(numpy.diff(shape.vertices, axis=0))
|
||||
half_width = rint_cast(shape.width / 2)
|
||||
path_type = next(k for k, v in path_cap_map.items() if v == shape.cap) # reverse lookup
|
||||
extension_start = (path_type, shape.cap_extensions[0] if shape.cap_extensions is not None else None)
|
||||
extension_end = (path_type, shape.cap_extensions[1] if shape.cap_extensions is not None else None)
|
||||
path = fatrec.Path(
|
||||
layer=layer,
|
||||
datatype=datatype,
|
||||
point_list=cast(Sequence[Sequence[int]], deltas),
|
||||
half_width=cast(int, half_width),
|
||||
x=xy[0],
|
||||
y=xy[1],
|
||||
extension_start=extension_start, # TODO implement multiple cap types?
|
||||
extension_end=extension_end,
|
||||
properties=properties,
|
||||
repetition=repetition,
|
||||
)
|
||||
elements.append(path)
|
||||
else:
|
||||
for polygon in shape.to_polygons():
|
||||
xy = rint_cast(polygon.offset + polygon.vertices[0] + rep_offset)
|
||||
points = rint_cast(numpy.diff(polygon.vertices, axis=0))
|
||||
elements.append(fatrec.Polygon(
|
||||
for mlayer, sseq in shapes.items():
|
||||
layer, datatype = layer2oas(mlayer)
|
||||
for shape in sseq:
|
||||
repetition, rep_offset = repetition_masq2fata(shape.repetition)
|
||||
properties = annotations_to_properties(shape.annotations)
|
||||
if isinstance(shape, Circle):
|
||||
offset = rint_cast(shape.offset + rep_offset)
|
||||
radius = rint_cast(shape.radius)
|
||||
circle = fatrec.Circle(
|
||||
layer=layer,
|
||||
datatype=datatype,
|
||||
x=xy[0],
|
||||
y=xy[1],
|
||||
point_list=cast(list[list[int]], points),
|
||||
radius=cast(int, radius),
|
||||
x=offset[0],
|
||||
y=offset[1],
|
||||
properties=properties,
|
||||
repetition=repetition,
|
||||
))
|
||||
)
|
||||
elements.append(circle)
|
||||
elif isinstance(shape, Path):
|
||||
xy = rint_cast(shape.offset + shape.vertices[0] + rep_offset)
|
||||
deltas = rint_cast(numpy.diff(shape.vertices, axis=0))
|
||||
half_width = rint_cast(shape.width / 2)
|
||||
path_type = next(k for k, v in path_cap_map.items() if v == shape.cap) # reverse lookup
|
||||
extension_start = (path_type, shape.cap_extensions[0] if shape.cap_extensions is not None else None)
|
||||
extension_end = (path_type, shape.cap_extensions[1] if shape.cap_extensions is not None else None)
|
||||
path = fatrec.Path(
|
||||
layer=layer,
|
||||
datatype=datatype,
|
||||
point_list=cast(Sequence[Sequence[int]], deltas),
|
||||
half_width=cast(int, half_width),
|
||||
x=xy[0],
|
||||
y=xy[1],
|
||||
extension_start=extension_start, # TODO implement multiple cap types?
|
||||
extension_end=extension_end,
|
||||
properties=properties,
|
||||
repetition=repetition,
|
||||
)
|
||||
elements.append(path)
|
||||
else:
|
||||
for polygon in shape.to_polygons():
|
||||
xy = rint_cast(polygon.offset + polygon.vertices[0] + rep_offset)
|
||||
points = rint_cast(numpy.diff(polygon.vertices, axis=0))
|
||||
elements.append(fatrec.Polygon(
|
||||
layer=layer,
|
||||
datatype=datatype,
|
||||
x=xy[0],
|
||||
y=xy[1],
|
||||
point_list=cast(list[list[int]], points),
|
||||
properties=properties,
|
||||
repetition=repetition,
|
||||
))
|
||||
return elements
|
||||
|
||||
|
||||
def _labels_to_texts(
|
||||
labels: list[Label],
|
||||
labels: dict[layer_t, list[Label]],
|
||||
layer2oas: Callable[[layer_t], tuple[int, int]],
|
||||
) -> list[fatrec.Text]:
|
||||
texts = []
|
||||
for label in labels:
|
||||
layer, datatype = layer2oas(label.layer)
|
||||
repetition, rep_offset = repetition_masq2fata(label.repetition)
|
||||
xy = rint_cast(label.offset + rep_offset)
|
||||
properties = annotations_to_properties(label.annotations)
|
||||
texts.append(fatrec.Text(
|
||||
layer=layer,
|
||||
datatype=datatype,
|
||||
x=xy[0],
|
||||
y=xy[1],
|
||||
string=label.string,
|
||||
properties=properties,
|
||||
repetition=repetition,
|
||||
))
|
||||
for mlayer, lseq in labels.items():
|
||||
layer, datatype = layer2oas(mlayer)
|
||||
for label in lseq:
|
||||
repetition, rep_offset = repetition_masq2fata(label.repetition)
|
||||
xy = rint_cast(label.offset + rep_offset)
|
||||
properties = annotations_to_properties(label.annotations)
|
||||
texts.append(fatrec.Text(
|
||||
layer=layer,
|
||||
datatype=datatype,
|
||||
x=xy[0],
|
||||
y=xy[1],
|
||||
string=label.string,
|
||||
properties=properties,
|
||||
repetition=repetition,
|
||||
))
|
||||
return texts
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -65,22 +65,24 @@ def writefile(
|
|||
for name, pat in library.items():
|
||||
svg_group = svg.g(id=mangle_name(name), fill='blue', stroke='red')
|
||||
|
||||
for shape in pat.shapes:
|
||||
for polygon in shape.to_polygons():
|
||||
path_spec = poly2path(polygon.vertices + polygon.offset)
|
||||
for layer, shapes in pat.shapes.items():
|
||||
for shape in shapes:
|
||||
for polygon in shape.to_polygons():
|
||||
path_spec = poly2path(polygon.vertices + polygon.offset)
|
||||
|
||||
path = svg.path(d=path_spec)
|
||||
if custom_attributes:
|
||||
path['pattern_layer'] = polygon.layer
|
||||
path = svg.path(d=path_spec)
|
||||
if custom_attributes:
|
||||
path['pattern_layer'] = layer
|
||||
|
||||
svg_group.add(path)
|
||||
svg_group.add(path)
|
||||
|
||||
for ref in pat.refs:
|
||||
if ref.target is None:
|
||||
for target, refs in pat.refs.items():
|
||||
if target is None:
|
||||
continue
|
||||
transform = f'scale({ref.scale:g}) rotate({ref.rotation:g}) translate({ref.offset[0]:g},{ref.offset[1]:g})'
|
||||
use = svg.use(href='#' + mangle_name(ref.target), transform=transform)
|
||||
svg_group.add(use)
|
||||
for ref in refs:
|
||||
transform = f'scale({ref.scale:g}) rotate({ref.rotation:g}) translate({ref.offset[0]:g},{ref.offset[1]:g})'
|
||||
use = svg.use(href='#' + mangle_name(target), transform=transform)
|
||||
svg_group.add(use)
|
||||
|
||||
svg.defs.add(svg_group)
|
||||
svg.add(svg.use(href='#' + mangle_name(top)))
|
||||
|
|
@ -133,9 +135,10 @@ def writefile_inverted(
|
|||
path_spec = poly2path(slab_edge)
|
||||
|
||||
# Draw polygons with reversed vertex order
|
||||
for shape in pattern.shapes:
|
||||
for polygon in shape.to_polygons():
|
||||
path_spec += poly2path(polygon.vertices[::-1] + polygon.offset)
|
||||
for _layer, shapes in pattern.shapes.items():
|
||||
for shape in shapes:
|
||||
for polygon in shape.to_polygons():
|
||||
path_spec += poly2path(polygon.vertices[::-1] + polygon.offset)
|
||||
|
||||
svg.add(svg.path(d=path_spec, fill='blue', stroke='red'))
|
||||
svg.save()
|
||||
|
|
|
|||
|
|
@ -42,16 +42,17 @@ def clean_pattern_vertices(pat: Pattern) -> Pattern:
|
|||
Returns:
|
||||
pat
|
||||
"""
|
||||
remove_inds = []
|
||||
for ii, shape in enumerate(pat.shapes):
|
||||
if not isinstance(shape, (Polygon, Path)):
|
||||
continue
|
||||
try:
|
||||
shape.clean_vertices()
|
||||
except PatternError:
|
||||
remove_inds.append(ii)
|
||||
for ii in sorted(remove_inds, reverse=True):
|
||||
del pat.shapes[ii]
|
||||
for shapes in pat.shapes.values():
|
||||
remove_inds = []
|
||||
for ii, shape in enumerate(shapes):
|
||||
if not isinstance(shape, (Polygon, Path)):
|
||||
continue
|
||||
try:
|
||||
shape.clean_vertices()
|
||||
except PatternError:
|
||||
remove_inds.append(ii)
|
||||
for ii in sorted(remove_inds, reverse=True):
|
||||
del shapes[ii]
|
||||
return pat
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue