more wip -- most central stuff is first pass done

This commit is contained in:
Jan Petykiewicz 2023-01-22 16:59:32 -08:00 committed by jan
commit 557c6c98dc
12 changed files with 405 additions and 598 deletions

View file

@ -17,6 +17,7 @@ from .. import Pattern, Ref, PatternError, Label, Shape
from ..shapes import Polygon, Path
from ..repetition import Grid
from ..utils import rotation_matrix_2d, layer_t
from .gdsii import check_valid_names
logger = logging.getLogger(__name__)
@ -33,7 +34,6 @@ def write(
library: Mapping[str, Pattern],
stream: io.TextIOBase,
*,
modify_originals: bool = False,
dxf_version='AC1024',
) -> None:
"""
@ -49,8 +49,8 @@ def write(
tuple: (1, 2) -> '1.2'
str: '1.2' -> '1.2' (no change)
It is often a good idea to run `pattern.dedup()` prior to calling this function,
especially if calling `.polygonize()` will result in very many vertices.
DXF does not support shape repetition (only block repeptition). Please call
library.wrap_repeated_shapes() before writing to file.
If you want pattern polygonized with non-default arguments, just call `pattern.polygonize()`
prior to calling this function.
@ -64,20 +64,13 @@ def write(
library: A {name: Pattern} mapping of patterns. Only `top_name` and patterns referenced
by it are written.
stream: Stream object to write to.
modify_original: If `True`, the original pattern is modified as part of the writing
process. Otherwise, a copy is made.
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`.
WARNING: No additional error checking is performed on the results.
"""
#TODO consider supporting DXF arcs?
#TODO name checking
bad_keys = check_valid_names(library.keys())
if not modify_originals:
library = library.deepcopy()
check_valid_names(library.keys())
pattern = library[top_name]
@ -329,6 +322,10 @@ def _shapes_to_elements(
# 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.')
attribs = {'layer': _mlayer2dxf(shape.layer)}
for polygon in shape.to_polygons():
xy_open = polygon.vertices + polygon.offset

View file

@ -29,6 +29,8 @@ import struct
import logging
import pathlib
import gzip
import string
from pprint import pformat
import numpy
from numpy.typing import NDArray, ArrayLike
@ -36,7 +38,7 @@ import klamath
from klamath import records
from .utils import is_gzipped
from .. import Pattern, Ref, PatternError, Label, Shape
from .. import Pattern, Ref, PatternError, LibraryError, Label, Shape
from ..shapes import Polygon, Path
from ..repetition import Grid
from ..utils import layer_t, normalize_mirror, annotations_t
@ -64,8 +66,6 @@ def write(
meters_per_unit: float,
logical_units_per_unit: float = 1,
library_name: str = 'masque-klamath',
*,
modify_originals: bool = False,
) -> None:
"""
Convert a library to a GDSII stream, mapping data as follows:
@ -82,8 +82,8 @@ def write(
datatype is chosen to be `shape.layer[1]` if available,
otherwise `0`
It is often a good idea to run `pattern.dedup()` prior to calling this function,
especially if calling `.polygonize()` will result in very many vertices.
GDS does not support shape repetition (only cell repeptition). Please call
library.wrap_repeated_shapes() before writing to file.
If you want pattern polygonized with non-default arguments, just call `pattern.polygonize()`
prior to calling this function.
@ -97,26 +97,17 @@ def write(
Default `1`.
library_name: Library name written into the GDSII file.
Default 'masque-klamath'.
modify_originals: If `True`, the original pattern is modified as part of the writing
process. Otherwise, a copy is made.
Default `False`.
"""
# TODO check name errors
bad_keys = check_valid_names(library.keys())
check_valid_names(library.keys())
# TODO check all hierarchy present
if not modify_originals:
library = copy.deepcopy(library) #TODO figure out best approach e.g. if lazy
if not isinstance(library, MutableLibrary):
if isinstance(library, dict):
library = WrapLibrary(library)
else:
library = WrapLibrary(dict(library))
library.wrap_repeated_shapes()
# Create library
header = klamath.library.FileHeader(
name=library_name.encode('ASCII'),
@ -440,6 +431,10 @@ def _shapes_to_elements(
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.')
layer, data_type = _mlayer2gds(shape.layer)
properties = _annotations_to_properties(shape.annotations, 128)
if isinstance(shape, Path) and not polygonize_paths:
@ -652,3 +647,37 @@ def load_libraryfile(
else:
stream = io.BufferedReader(base_stream)
return load_library(stream, full_load=full_load)
def check_valid_names(
names: Iterable[str],
max_length: int = 32,
) -> None:
"""
Check all provided names to see if they're valid GDSII cell names.
Args:
names: Collection of names to check
max_length: Max allowed length
"""
allowed_chars = set(string.ascii_letters + string.digits + '_?$')
bad_chars = [
name for name in names
if not set(name).issubset(allowed_chars)
]
bad_lengths = [
name for name in names
if len(name) > max_length
]
if bad_chars:
logger.error('Names contain invalid characters:\n' + pformat(bad_chars))
if bad_lengths:
logger.error(f'Names too long (>{max_length}:\n' + pformat(bad_chars))
if bad_chars or bad_lengths:
raise LibraryError('Library contains invalid names, see log above')

View file

@ -20,6 +20,8 @@ import struct
import logging
import pathlib
import gzip
import string
from pprint import pformat
import numpy
from numpy.typing import ArrayLike, NDArray
@ -28,7 +30,7 @@ import fatamorgana.records as fatrec
from fatamorgana.basic import PathExtensionScheme, AString, NString, PropStringReference
from .utils import is_gzipped
from .. import Pattern, Ref, PatternError, Label, Shape
from .. import Pattern, Ref, PatternError, LibraryError, Label, Shape
from ..library import WrapLibrary, MutableLibrary
from ..shapes import Polygon, Path, Circle
from ..repetition import Grid, Arbitrary, Repetition
@ -95,9 +97,7 @@ def build(
Returns:
`fatamorgana.OasisLayout`
"""
# TODO check names
bad_keys = check_valid_names(library.keys())
check_valid_names(library.keys())
# TODO check all hierarchy present
@ -110,9 +110,6 @@ def build(
if layer_map is None:
layer_map = {}
if disambiguate_func is None:
disambiguate_func = disambiguate_pattern_names
if annotations is None:
annotations = {}
@ -616,32 +613,6 @@ def _labels_to_texts(
return texts
def disambiguate_pattern_names(
names: Iterable[str],
) -> List[str]:
new_names = []
for name in names:
sanitized_name = re.compile(r'[^A-Za-z0-9_\?\$]').sub('_', name)
i = 0
suffixed_name = sanitized_name
while suffixed_name in new_names or suffixed_name == '':
suffix = base64.b64encode(struct.pack('>Q', i), b'$?').decode('ASCII')
suffixed_name = sanitized_name + '$' + suffix[:-1].lstrip('A')
i += 1
if sanitized_name == '':
logger.warning(f'Empty pattern name saved as "{suffixed_name}"')
if len(suffixed_name) == 0:
# Should never happen since zero-length names are replaced
raise PatternError(f'Zero-length name after sanitize+encode,\n originally "{name}"')
new_names.append(suffixed_name)
return new_names
def repetition_fata2masq(
rep: Union[fatamorgana.GridRepetition, fatamorgana.ArbitraryRepetition, None],
) -> Optional[Repetition]:
@ -734,3 +705,25 @@ def properties_to_annotations(
properties = [fatrec.Property(key, vals, is_standard=False)
for key, vals in annotations.items()]
return properties
def check_valid_names(
names: Iterable[str],
) -> None:
"""
Check all provided names to see if they're valid GDSII cell names.
Args:
names: Collection of names to check
max_length: Max allowed length
"""
allowed_chars = set(string.ascii_letters + string.digits + string.punctuation + ' ')
bad_chars = [
name for name in names
if not set(name).issubset(allowed_chars)
]
if bad_chars:
raise LibraryError('Names contain invalid characters:\n' + pformat(bad_chars))