add support for annotations
and other fixes
This commit is contained in:
parent
5d83e0e5c0
commit
49a3b4e322
28 changed files with 400 additions and 133 deletions
|
|
@ -10,10 +10,10 @@ import struct
|
|||
import logging
|
||||
import pathlib
|
||||
import gzip
|
||||
import numpy
|
||||
from numpy import pi
|
||||
|
||||
import ezdxf
|
||||
import numpy # type: ignore
|
||||
from numpy import pi
|
||||
import ezdxf # type: ignore
|
||||
|
||||
from .utils import mangle_name, make_dose_table
|
||||
from .. import Pattern, SubPattern, PatternError, Label, Shape
|
||||
|
|
@ -264,13 +264,12 @@ def _read_block(block, clean_vertices):
|
|||
}
|
||||
|
||||
if 'column_count' in attr:
|
||||
args['a_vector'] = (attr['column_spacing'], 0)
|
||||
args['b_vector'] = (0, attr['row_spacing'])
|
||||
args['a_count'] = attr['column_count']
|
||||
args['b_count'] = attr['row_count']
|
||||
pat.subpatterns.append(GridRepetition(**args))
|
||||
else:
|
||||
pat.subpatterns.append(SubPattern(**args))
|
||||
args['repetition'] = Grid(
|
||||
a_vector=(attr['column_spacing'], 0),
|
||||
b_vector=(0, attr['row_spacing']),
|
||||
a_count=attr['column_count'],
|
||||
b_count=attr['row_count'])
|
||||
pat.subpatterns.append(SubPattern(**args))
|
||||
else:
|
||||
logger.warning(f'Ignoring DXF element {element.dxftype()} (not implemented).')
|
||||
return pat
|
||||
|
|
|
|||
|
|
@ -11,17 +11,18 @@ Note that GDSII references follow the same convention as `masque`,
|
|||
Scaling, rotation, and mirroring apply to individual instances, not grid
|
||||
vectors or offsets.
|
||||
"""
|
||||
from typing import List, Any, Dict, Tuple, Callable, Union, Sequence, Iterable, Optional, Sequence
|
||||
from typing import List, Any, Dict, Tuple, Callable, Union, Sequence, Iterable, Optional
|
||||
from typing import Sequence, Mapping
|
||||
import re
|
||||
import io
|
||||
import copy
|
||||
import numpy
|
||||
import base64
|
||||
import struct
|
||||
import logging
|
||||
import pathlib
|
||||
import gzip
|
||||
|
||||
import numpy # type: ignore
|
||||
# python-gdsii
|
||||
import gdsii.library
|
||||
import gdsii.structure
|
||||
|
|
@ -32,7 +33,7 @@ from .. import Pattern, SubPattern, PatternError, Label, Shape
|
|||
from ..shapes import Polygon, Path
|
||||
from ..repetition import Grid
|
||||
from ..utils import rotation_matrix_2d, get_bit, set_bit, vector2, is_scalar, layer_t
|
||||
from ..utils import remove_colinear_vertices, normalize_mirror
|
||||
from ..utils import remove_colinear_vertices, normalize_mirror, annotations_t
|
||||
|
||||
#TODO absolute positioning
|
||||
|
||||
|
|
@ -99,6 +100,7 @@ def build(patterns: Union[Pattern, List[Pattern]],
|
|||
|
||||
if disambiguate_func is None:
|
||||
disambiguate_func = disambiguate_pattern_names
|
||||
assert(disambiguate_func is not None) # placate mypy
|
||||
|
||||
if not modify_originals:
|
||||
patterns = [p.deepunlock() for p in copy.deepcopy(patterns)]
|
||||
|
|
@ -124,6 +126,8 @@ def build(patterns: Union[Pattern, List[Pattern]],
|
|||
structure = gdsii.structure.Structure(name=pat.name.encode('ASCII'))
|
||||
lib.append(structure)
|
||||
|
||||
structure.properties = _annotations_to_properties(pat.annotations, 512)
|
||||
|
||||
structure += _shapes_to_elements(pat.shapes)
|
||||
structure += _labels_to_texts(pat.labels)
|
||||
structure += _subpatterns_to_refs(pat.subpatterns)
|
||||
|
|
@ -238,6 +242,9 @@ def read(stream: io.BufferedIOBase,
|
|||
patterns = []
|
||||
for structure in lib:
|
||||
pat = Pattern(name=structure.name.decode('ASCII'))
|
||||
if pat.annotations:
|
||||
logger.warning('Dropping Pattern-level annotations; they are not supported by python-gdsii')
|
||||
# pat.annotations = {str(k): v for k, v in structure.properties}
|
||||
for element in structure:
|
||||
# Switch based on element type:
|
||||
if isinstance(element, gdsii.elements.Boundary):
|
||||
|
|
@ -343,6 +350,7 @@ def _ref_to_subpat(element: Union[gdsii.elements.SRef,
|
|||
rotation=rotation,
|
||||
scale=scale,
|
||||
mirrored=(mirror_across_x, False),
|
||||
annotations=_properties_to_annotations(element.properties),
|
||||
repetition=repetition)
|
||||
subpat.identifier = (element.struct_name,)
|
||||
return subpat
|
||||
|
|
@ -359,6 +367,7 @@ def _gpath_to_mpath(element: gdsii.elements.Path, raw_mode: bool) -> Path:
|
|||
'width': element.width if element.width is not None else 0.0,
|
||||
'cap': cap,
|
||||
'offset': numpy.zeros(2),
|
||||
'annotations':_properties_to_annotations(element.properties),
|
||||
'raw': raw_mode,
|
||||
}
|
||||
|
||||
|
|
@ -376,6 +385,7 @@ def _boundary_to_polygon(element: gdsii.elements.Boundary, raw_mode: bool) -> Po
|
|||
args = {'vertices': element.xy[:-1].astype(float),
|
||||
'layer': (element.layer, element.data_type),
|
||||
'offset': numpy.zeros(2),
|
||||
'annotations':_properties_to_annotations(element.properties),
|
||||
'raw': raw_mode,
|
||||
}
|
||||
return Polygon(**args)
|
||||
|
|
@ -420,11 +430,38 @@ def _subpatterns_to_refs(subpatterns: List[SubPattern]
|
|||
# strans must be non-None for angle and mag to take effect
|
||||
ref.strans = set_bit(0, 15 - 0, mirror_across_x)
|
||||
ref.mag = subpat.scale
|
||||
ref.properties = _annotations_to_properties(subpat.annotations, 512)
|
||||
|
||||
refs += new_refs
|
||||
return refs
|
||||
|
||||
|
||||
def _properties_to_annotations(properties: List[Tuple[int, bytes]]) -> annotations_t:
|
||||
return {str(k): [v.decode()] for k, v in properties}
|
||||
|
||||
|
||||
def _annotations_to_properties(annotations: annotations_t, max_len: int = 126) -> List[Tuple[int, bytes]]:
|
||||
cum_len = 0
|
||||
props = []
|
||||
for key, vals in annotations.items():
|
||||
try:
|
||||
i = int(key)
|
||||
except:
|
||||
raise PatternError(f'Annotation key {key} is not convertable to an integer')
|
||||
if not (0 < i < 126):
|
||||
raise PatternError(f'Annotation key {key} converts to {i} (must be in the range [1,125])')
|
||||
|
||||
val_strings = ' '.join(str(val) for val in vals)
|
||||
b = val_strings.encode()
|
||||
if len(b) > 126:
|
||||
raise PatternError(f'Annotation value {b!r} is longer than 126 characters!')
|
||||
cum_len += numpy.ceil(len(b) / 2) * 2 + 2
|
||||
if cum_len > max_len:
|
||||
raise PatternError(f'Sum of annotation data will be longer than {max_len} bytes! Generated bytes were {b!r}')
|
||||
props.append((i, b))
|
||||
return props
|
||||
|
||||
|
||||
def _shapes_to_elements(shapes: List[Shape],
|
||||
polygonize_paths: bool = False
|
||||
) -> List[Union[gdsii.elements.Boundary, gdsii.elements.Path]]:
|
||||
|
|
@ -432,6 +469,7 @@ def _shapes_to_elements(shapes: List[Shape],
|
|||
# Add a Boundary element for each shape, and Path elements if necessary
|
||||
for shape in shapes:
|
||||
layer, data_type = _mlayer2gds(shape.layer)
|
||||
properties = _annotations_to_properties(shape.annotations, 128)
|
||||
if isinstance(shape, Path) and not polygonize_paths:
|
||||
xy = numpy.round(shape.vertices + shape.offset).astype(int)
|
||||
width = numpy.round(shape.width).astype(int)
|
||||
|
|
@ -441,26 +479,32 @@ def _shapes_to_elements(shapes: List[Shape],
|
|||
xy=xy)
|
||||
path.path_type = path_type
|
||||
path.width = width
|
||||
path.properties = properties
|
||||
elements.append(path)
|
||||
else:
|
||||
for polygon in shape.to_polygons():
|
||||
xy_open = numpy.round(polygon.vertices + polygon.offset).astype(int)
|
||||
xy_closed = numpy.vstack((xy_open, xy_open[0, :]))
|
||||
elements.append(gdsii.elements.Boundary(layer=layer,
|
||||
data_type=data_type,
|
||||
xy=xy_closed))
|
||||
boundary = gdsii.elements.Boundary(layer=layer,
|
||||
data_type=data_type,
|
||||
xy=xy_closed)
|
||||
boundary.properties = properties
|
||||
elements.append(boundary)
|
||||
return elements
|
||||
|
||||
|
||||
def _labels_to_texts(labels: List[Label]) -> List[gdsii.elements.Text]:
|
||||
texts = []
|
||||
for label in labels:
|
||||
properties = _annotations_to_properties(label.annotations, 128)
|
||||
layer, text_type = _mlayer2gds(label.layer)
|
||||
xy = numpy.round([label.offset]).astype(int)
|
||||
texts.append(gdsii.elements.Text(layer=layer,
|
||||
text_type=text_type,
|
||||
xy=xy,
|
||||
string=label.string.encode('ASCII')))
|
||||
text = gdsii.elements.Text(layer=layer,
|
||||
text_type=text_type,
|
||||
xy=xy,
|
||||
string=label.string.encode('ASCII'))
|
||||
text.properties = properties
|
||||
texts.append(text)
|
||||
return texts
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -20,19 +20,19 @@ import struct
|
|||
import logging
|
||||
import pathlib
|
||||
import gzip
|
||||
import numpy
|
||||
from numpy import pi
|
||||
|
||||
import numpy # type: ignore
|
||||
from numpy import pi
|
||||
import fatamorgana
|
||||
import fatamorgana.records as fatrec
|
||||
from fatamorgana.basic import PathExtensionScheme
|
||||
from fatamorgana.basic import PathExtensionScheme, AString, NString, PropStringReference
|
||||
|
||||
from .utils import mangle_name, make_dose_table
|
||||
from .. import Pattern, SubPattern, PatternError, Label, Shape
|
||||
from ..shapes import Polygon, Path, Circle
|
||||
from ..repetition import Grid, Arbitrary, Repetition
|
||||
from ..utils import rotation_matrix_2d, get_bit, set_bit, vector2, is_scalar, layer_t
|
||||
from ..utils import remove_colinear_vertices, normalize_mirror
|
||||
from ..utils import remove_colinear_vertices, normalize_mirror, annotations_t
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -52,9 +52,11 @@ path_cap_map = {
|
|||
|
||||
def build(patterns: Union[Pattern, List[Pattern]],
|
||||
units_per_micron: int,
|
||||
layer_map: Dict[str, Union[int, Tuple[int, int]]] = None,
|
||||
layer_map: Optional[Dict[str, Union[int, Tuple[int, int]]]] = None,
|
||||
*,
|
||||
modify_originals: bool = False,
|
||||
disambiguate_func: Callable[[Iterable[Pattern]], None] = None,
|
||||
disambiguate_func: Optional[Callable[[Iterable[Pattern]], None]] = None,
|
||||
annotations: Optional[annotations_t] = None
|
||||
) -> fatamorgana.OasisLayout:
|
||||
"""
|
||||
Convert a `Pattern` or list of patterns to an OASIS stream, writing patterns
|
||||
|
|
@ -91,6 +93,7 @@ def build(patterns: Union[Pattern, List[Pattern]],
|
|||
Default `False`.
|
||||
disambiguate_func: Function which takes a list of patterns and alters them
|
||||
to make their names valid and unique. Default is `disambiguate_pattern_names`.
|
||||
annotations: dictionary of key-value pairs which are saved as library-level properties
|
||||
|
||||
Returns:
|
||||
`fatamorgana.OasisLayout`
|
||||
|
|
@ -104,11 +107,15 @@ def build(patterns: Union[Pattern, List[Pattern]],
|
|||
if disambiguate_func is None:
|
||||
disambiguate_func = disambiguate_pattern_names
|
||||
|
||||
if annotations is None:
|
||||
annotations = {}
|
||||
|
||||
if not modify_originals:
|
||||
patterns = [p.deepunlock() for p in copy.deepcopy(patterns)]
|
||||
|
||||
# Create library
|
||||
lib = fatamorgana.OasisLayout(unit=units_per_micron, validation=None)
|
||||
lib.properties = annotations_to_properties(annotations)
|
||||
|
||||
if layer_map:
|
||||
for name, layer_num in layer_map.items():
|
||||
|
|
@ -139,9 +146,11 @@ def build(patterns: Union[Pattern, List[Pattern]],
|
|||
structure = fatamorgana.Cell(name=pat.name)
|
||||
lib.cells.append(structure)
|
||||
|
||||
structure.properties += annotations_to_properties(pat.annotations)
|
||||
|
||||
structure.geometry += _shapes_to_elements(pat.shapes, layer2oas)
|
||||
structure.geometry += _labels_to_texts(pat.labels, layer2oas)
|
||||
structure.placements += _subpatterns_to_refs(pat.subpatterns)
|
||||
structure.placements += _subpatterns_to_placements(pat.subpatterns)
|
||||
|
||||
return lib
|
||||
|
||||
|
|
@ -226,6 +235,8 @@ def read(stream: io.BufferedIOBase,
|
|||
|
||||
Additional library info is returned in a dict, containing:
|
||||
'units_per_micrometer': number of database units per micrometer (all values are in database units)
|
||||
'layer_map': Mapping from layer names to fatamorgana.LayerName objects
|
||||
'annotations': Mapping of {key: value} pairs from library's properties
|
||||
|
||||
Args:
|
||||
stream: Stream to read from.
|
||||
|
|
@ -242,6 +253,7 @@ def read(stream: io.BufferedIOBase,
|
|||
|
||||
library_info: Dict[str, Any] = {
|
||||
'units_per_micrometer': lib.unit,
|
||||
'annotations': properties_to_annotations(lib.properties, lib.propnames, lib.propstrings),
|
||||
}
|
||||
|
||||
layer_map = {}
|
||||
|
|
@ -252,7 +264,7 @@ def read(stream: io.BufferedIOBase,
|
|||
patterns = []
|
||||
for cell in lib.cells:
|
||||
if isinstance(cell.name, int):
|
||||
cell_name = lib.cellnames[cell.name].string
|
||||
cell_name = lib.cellnames[cell.name].nstring.string
|
||||
else:
|
||||
cell_name = cell.name.string
|
||||
|
||||
|
|
@ -263,15 +275,16 @@ def read(stream: io.BufferedIOBase,
|
|||
# note XELEMENT has no repetition
|
||||
continue
|
||||
|
||||
|
||||
repetition = repetition_fata2masq(element.repetition)
|
||||
|
||||
# Switch based on element type:
|
||||
if isinstance(element, fatrec.Polygon):
|
||||
vertices = numpy.cumsum(numpy.vstack(((0, 0), element.get_point_list())), axis=0)
|
||||
annotations = properties_to_annotations(element.properties, lib.propnames, lib.propstrings)
|
||||
poly = Polygon(vertices=vertices,
|
||||
layer=element.get_layer_tuple(),
|
||||
offset=element.get_xy(),
|
||||
annotations=annotations,
|
||||
repetition=repetition)
|
||||
|
||||
if clean_vertices:
|
||||
|
|
@ -295,10 +308,13 @@ def read(stream: io.BufferedIOBase,
|
|||
if cap == Path.Cap.SquareCustom:
|
||||
path_args['cap_extensions'] = numpy.array((element.get_extension_start()[1],
|
||||
element.get_extension_end()[1]))
|
||||
|
||||
annotations = properties_to_annotations(element.properties, lib.propnames, lib.propstrings)
|
||||
path = Path(vertices=vertices,
|
||||
layer=element.get_layer_tuple(),
|
||||
offset=element.get_xy(),
|
||||
repetition=repetition,
|
||||
annotations=annotations,
|
||||
width=element.get_half_width() * 2,
|
||||
cap=cap,
|
||||
**path_args)
|
||||
|
|
@ -314,10 +330,12 @@ def read(stream: io.BufferedIOBase,
|
|||
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(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)
|
||||
|
||||
|
|
@ -346,10 +364,12 @@ def read(stream: io.BufferedIOBase,
|
|||
else:
|
||||
vertices[2, 0] -= b
|
||||
|
||||
annotations = properties_to_annotations(element.properties, lib.propnames, lib.propstrings)
|
||||
trapz = Polygon(layer=element.get_layer_tuple(),
|
||||
offset=element.get_xy(),
|
||||
repetition=repetition,
|
||||
vertices=vertices,
|
||||
annotations=annotations,
|
||||
)
|
||||
pat.shapes.append(trapz)
|
||||
|
||||
|
|
@ -399,24 +419,30 @@ def read(stream: io.BufferedIOBase,
|
|||
vertices = vertices[[0, 2, 3], :]
|
||||
vertices[0, 1] += width
|
||||
|
||||
annotations = properties_to_annotations(element.properties, lib.propnames, lib.propstrings)
|
||||
ctrapz = 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)
|
||||
circle = Circle(layer=element.get_layer_tuple(),
|
||||
offset=element.get_xy(),
|
||||
repetition=repetition,
|
||||
annotations=annotations,
|
||||
radius=float(element.get_radius()))
|
||||
pat.shapes.append(circle)
|
||||
|
||||
elif isinstance(element, fatrec.Text):
|
||||
annotations = properties_to_annotations(element.properties, lib.propnames, lib.propstrings)
|
||||
label = Label(layer=element.get_layer_tuple(),
|
||||
offset=element.get_xy(),
|
||||
repetition=repetition,
|
||||
annotations=annotations,
|
||||
string=str(element.get_string()))
|
||||
pat.labels.append(label)
|
||||
|
||||
|
|
@ -425,7 +451,7 @@ def read(stream: io.BufferedIOBase,
|
|||
continue
|
||||
|
||||
for placement in cell.placements:
|
||||
pat.subpatterns.append(_placement_to_subpat(placement))
|
||||
pat.subpatterns.append(_placement_to_subpat(placement, lib))
|
||||
|
||||
patterns.append(pat)
|
||||
|
||||
|
|
@ -435,7 +461,7 @@ def read(stream: io.BufferedIOBase,
|
|||
for p in patterns_dict.values():
|
||||
for sp in p.subpatterns:
|
||||
ident = sp.identifier[0]
|
||||
name = ident if isinstance(ident, str) else lib.cellnames[ident].string
|
||||
name = ident if isinstance(ident, str) else lib.cellnames[ident].nstring.string
|
||||
sp.pattern = patterns_dict[name]
|
||||
del sp.identifier
|
||||
|
||||
|
|
@ -459,7 +485,7 @@ def _mlayer2oas(mlayer: layer_t) -> Tuple[int, int]:
|
|||
return layer, data_type
|
||||
|
||||
|
||||
def _placement_to_subpat(placement: fatrec.Placement) -> SubPattern:
|
||||
def _placement_to_subpat(placement: fatrec.Placement, lib: fatamorgana.OasisLayout) -> SubPattern:
|
||||
"""
|
||||
Helper function to create a SubPattern from a placment. Sets subpat.pattern to None
|
||||
and sets the instance .identifier to (struct_name,).
|
||||
|
|
@ -468,21 +494,20 @@ def _placement_to_subpat(placement: fatrec.Placement) -> SubPattern:
|
|||
mag = placement.magnification if placement.magnification is not None else 1
|
||||
pname = placement.get_name()
|
||||
name = pname if isinstance(pname, int) else pname.string
|
||||
args: Dict[str, Any] = {
|
||||
'pattern': None,
|
||||
'mirrored': (placement.flip, False),
|
||||
'rotation': float(placement.angle * pi/180),
|
||||
'scale': mag,
|
||||
'identifier': (name,),
|
||||
'repetition': repetition_fata2masq(placement.repetition),
|
||||
}
|
||||
|
||||
subpat = SubPattern(offset=xy, **args)
|
||||
annotations = properties_to_annotations(placement.properties, lib.propnames, lib.propstrings)
|
||||
subpat = SubPattern(offset=xy,
|
||||
pattern=None,
|
||||
mirrored=(placement.flip, False),
|
||||
rotation=float(placement.angle * pi/180),
|
||||
scale=float(mag),
|
||||
identifier=(name,),
|
||||
repetition=repetition_fata2masq(placement.repetition),
|
||||
annotations=annotations)
|
||||
return subpat
|
||||
|
||||
|
||||
def _subpatterns_to_refs(subpatterns: List[SubPattern]
|
||||
) -> List[fatrec.Placement]:
|
||||
def _subpatterns_to_placements(subpatterns: List[SubPattern]
|
||||
) -> List[fatrec.Placement]:
|
||||
refs = []
|
||||
for subpat in subpatterns:
|
||||
if subpat.pattern is None:
|
||||
|
|
@ -493,19 +518,16 @@ def _subpatterns_to_refs(subpatterns: List[SubPattern]
|
|||
frep, rep_offset = repetition_masq2fata(subpat.repetition)
|
||||
|
||||
offset = numpy.round(subpat.offset + rep_offset).astype(int)
|
||||
args: Dict[str, Any] = {
|
||||
'x': offset[0],
|
||||
'y': offset[1],
|
||||
'repetition': frep,
|
||||
}
|
||||
|
||||
angle = numpy.rad2deg(subpat.rotation + extra_angle) % 360
|
||||
ref = fatrec.Placement(
|
||||
name=subpat.pattern.name,
|
||||
flip=mirror_across_x,
|
||||
angle=angle,
|
||||
magnification=subpat.scale,
|
||||
**args)
|
||||
properties=annotations_to_properties(subpat.annotations),
|
||||
x=offset[0],
|
||||
y=offset[1],
|
||||
repetition=frep)
|
||||
|
||||
refs.append(ref)
|
||||
return refs
|
||||
|
|
@ -519,6 +541,7 @@ def _shapes_to_elements(shapes: List[Shape],
|
|||
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 = numpy.round(shape.offset + rep_offset).astype(int)
|
||||
radius = numpy.round(shape.radius).astype(int)
|
||||
|
|
@ -527,6 +550,7 @@ def _shapes_to_elements(shapes: List[Shape],
|
|||
radius=radius,
|
||||
x=offset[0],
|
||||
y=offset[1],
|
||||
properties=properties,
|
||||
repetition=repetition)
|
||||
elements.append(circle)
|
||||
elif isinstance(shape, Path):
|
||||
|
|
@ -544,6 +568,7 @@ def _shapes_to_elements(shapes: List[Shape],
|
|||
y=xy[1],
|
||||
extension_start=extension_start, #TODO implement multiple cap types?
|
||||
extension_end=extension_end,
|
||||
properties=properties,
|
||||
repetition=repetition,
|
||||
)
|
||||
elements.append(path)
|
||||
|
|
@ -556,6 +581,7 @@ def _shapes_to_elements(shapes: List[Shape],
|
|||
x=xy[0],
|
||||
y=xy[1],
|
||||
point_list=points,
|
||||
properties=properties,
|
||||
repetition=repetition))
|
||||
return elements
|
||||
|
||||
|
|
@ -568,11 +594,13 @@ def _labels_to_texts(labels: List[Label],
|
|||
layer, datatype = layer2oas(label.layer)
|
||||
repetition, rep_offset = repetition_masq2fata(label.repetition)
|
||||
xy = numpy.round(label.offset + rep_offset).astype(int)
|
||||
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
|
||||
|
||||
|
|
@ -609,6 +637,7 @@ def disambiguate_pattern_names(patterns,
|
|||
|
||||
def repetition_fata2masq(rep: Union[fatamorgana.GridRepetition, fatamorgana.ArbitraryRepetition, None]
|
||||
) -> Optional[Repetition]:
|
||||
mrep: Optional[Repetition]
|
||||
if isinstance(rep, fatamorgana.GridRepetition):
|
||||
mrep = Grid(a_vector=rep.a_vector,
|
||||
b_vector=rep.b_vector,
|
||||
|
|
@ -624,7 +653,12 @@ def repetition_fata2masq(rep: Union[fatamorgana.GridRepetition, fatamorgana.Arbi
|
|||
return mrep
|
||||
|
||||
|
||||
def repetition_masq2fata(rep: Optional[Repetition]):
|
||||
def repetition_masq2fata(rep: Optional[Repetition]
|
||||
) -> Tuple[Union[fatamorgana.GridRepetition,
|
||||
fatamorgana.ArbitraryRepetition,
|
||||
None],
|
||||
Tuple[int, int]]:
|
||||
frep: Union[fatamorgana.GridRepetition, fatamorgana.ArbitraryRepetition, None]
|
||||
if isinstance(rep, Grid):
|
||||
frep = fatamorgana.GridRepetition(
|
||||
a_vector=numpy.round(rep.a_vector).astype(int),
|
||||
|
|
@ -642,3 +676,46 @@ def repetition_masq2fata(rep: Optional[Repetition]):
|
|||
frep = None
|
||||
offset = (0, 0)
|
||||
return frep, offset
|
||||
|
||||
|
||||
def annotations_to_properties(annotations: annotations_t) -> List[fatrec.Property]:
|
||||
#TODO determine is_standard based on key?
|
||||
properties = []
|
||||
for key, values in annotations.items():
|
||||
vals = [AString(v) if isinstance(v, str) else v
|
||||
for v in values]
|
||||
properties.append(fatrec.Property(key, vals, is_standard=False))
|
||||
return properties
|
||||
|
||||
|
||||
def properties_to_annotations(properties: List[fatrec.Property],
|
||||
propnames: Dict[int, NString],
|
||||
propstrings: Dict[int, AString],
|
||||
) -> annotations_t:
|
||||
annotations = {}
|
||||
for proprec in properties:
|
||||
assert(proprec.name is not None)
|
||||
if isinstance(proprec.name, int):
|
||||
key = propnames[proprec.name].string
|
||||
else:
|
||||
key = proprec.name.string
|
||||
values: List[Union[str, float, int]] = []
|
||||
|
||||
assert(proprec.values is not None)
|
||||
for value in proprec.values:
|
||||
if isinstance(value, (float, int)):
|
||||
values.append(value)
|
||||
elif isinstance(value, (NString, AString)):
|
||||
values.append(value.string)
|
||||
elif isinstance(value, PropStringReference):
|
||||
values.append(propstrings[value.ref].string) # dereference
|
||||
else:
|
||||
string = repr(value)
|
||||
logger.warning(f'Converting property value for key ({key}) to string ({string})')
|
||||
values.append(string)
|
||||
annotations[key] = values
|
||||
return annotations
|
||||
|
||||
properties = [fatrec.Property(key, vals, is_standard=False)
|
||||
for key, vals in annotations.items()]
|
||||
return properties
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
SVG file format readers and writers
|
||||
"""
|
||||
from typing import Dict, Optional
|
||||
import svgwrite
|
||||
import numpy
|
||||
import warnings
|
||||
|
||||
import numpy # type: ignore
|
||||
import svgwrite # type: ignore
|
||||
|
||||
from .utils import mangle_name
|
||||
from .. import Pattern
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue