more wip -- most central stuff is first pass done
This commit is contained in:
parent
6549faddbb
commit
557c6c98dc
12 changed files with 405 additions and 598 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue