style and type fixes (per flake8)
could potentially fix some bugs in `Library` class and dxf reader
This commit is contained in:
parent
f6ad272c2c
commit
f364970403
31 changed files with 293 additions and 297 deletions
|
|
@ -1,3 +1,4 @@
|
|||
"""
|
||||
Functions for reading from and writing to various file formats.
|
||||
"""
|
||||
"""
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
"""
|
||||
DXF file format readers and writers
|
||||
"""
|
||||
from typing import List, Any, Dict, Tuple, Callable, Union, Sequence, Iterable, Optional
|
||||
from typing import List, Any, Dict, Tuple, Callable, Union, Sequence, Iterable
|
||||
import re
|
||||
import io
|
||||
import copy
|
||||
import base64
|
||||
import struct
|
||||
import logging
|
||||
|
|
@ -12,15 +11,12 @@ import pathlib
|
|||
import gzip
|
||||
|
||||
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
|
||||
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 rotation_matrix_2d, layer_t
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -75,6 +71,7 @@ def write(pattern: Pattern,
|
|||
#TODO consider supporting DXF arcs?
|
||||
if disambiguate_func is None:
|
||||
disambiguate_func = disambiguate_pattern_names
|
||||
assert(disambiguate_func is not None)
|
||||
|
||||
if not modify_originals:
|
||||
pattern = pattern.deepcopy().deepunlock()
|
||||
|
|
@ -125,8 +122,7 @@ def writefile(pattern: Pattern,
|
|||
open_func = open
|
||||
|
||||
with open_func(path, mode='wt') as stream:
|
||||
results = write(pattern, stream, *args, **kwargs)
|
||||
return results
|
||||
write(pattern, stream, *args, **kwargs)
|
||||
|
||||
|
||||
def readfile(filename: Union[str, pathlib.Path],
|
||||
|
|
@ -204,25 +200,26 @@ def _read_block(block, clean_vertices: bool) -> Pattern:
|
|||
else:
|
||||
points = numpy.array(tuple(element.points()))
|
||||
attr = element.dxfattribs()
|
||||
args = {'layer': attr.get('layer', DEFAULT_LAYER),
|
||||
}
|
||||
layer = attr.get('layer', DEFAULT_LAYER)
|
||||
|
||||
if points.shape[1] == 2:
|
||||
shape = Polygon(**args)
|
||||
raise PatternError('Invalid or unimplemented polygon?')
|
||||
#shape = Polygon(layer=layer)
|
||||
elif points.shape[1] > 2:
|
||||
if (points[0, 2] != points[:, 2]).any():
|
||||
raise PatternError('PolyLine has non-constant width (not yet representable in masque!)')
|
||||
elif points.shape[1] == 4 and (points[:, 3] != 0).any():
|
||||
raise PatternError('LWPolyLine has bulge (not yet representable in masque!)')
|
||||
else:
|
||||
width = points[0, 2]
|
||||
if width == 0:
|
||||
width = attr.get('const_width', 0)
|
||||
|
||||
if width == 0 and numpy.array_equal(points[0], points[-1]):
|
||||
shape = Polygon(**args, vertices=points[:-1, :2])
|
||||
else:
|
||||
shape = Path(**args, width=width, vertices=points[:, :2])
|
||||
width = points[0, 2]
|
||||
if width == 0:
|
||||
width = attr.get('const_width', 0)
|
||||
|
||||
shape: Union[Path, Polygon]
|
||||
if width == 0 and numpy.array_equal(points[0], points[-1]):
|
||||
shape = Polygon(layer=layer, vertices=points[:-1, :2])
|
||||
else:
|
||||
shape = Path(layer=layer, width=width, vertices=points[:, :2])
|
||||
|
||||
if clean_vertices:
|
||||
try:
|
||||
|
|
@ -237,7 +234,7 @@ def _read_block(block, clean_vertices: bool) -> Pattern:
|
|||
'layer': element.dxfattribs().get('layer', DEFAULT_LAYER),
|
||||
}
|
||||
string = element.dxfattribs().get('text', '')
|
||||
height = element.dxfattribs().get('height', 0)
|
||||
# height = element.dxfattribs().get('height', 0)
|
||||
# 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.')
|
||||
|
|
@ -252,7 +249,7 @@ def _read_block(block, clean_vertices: bool) -> Pattern:
|
|||
logger.warning('Masque does not support per-axis scaling; using x-scaling only!')
|
||||
scale = abs(xscale)
|
||||
mirrored = (yscale < 0, xscale < 0)
|
||||
rotation = attr.get('rotation', 0) * pi/180
|
||||
rotation = numpy.deg2rad(attr.get('rotation', 0))
|
||||
|
||||
offset = attr.get('insert', (0, 0, 0))[:2]
|
||||
|
||||
|
|
@ -266,11 +263,10 @@ def _read_block(block, clean_vertices: bool) -> Pattern:
|
|||
}
|
||||
|
||||
if 'column_count' in attr:
|
||||
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'])
|
||||
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).')
|
||||
|
|
@ -356,11 +352,11 @@ def _mlayer2dxf(layer: layer_t) -> str:
|
|||
def disambiguate_pattern_names(patterns: Sequence[Pattern],
|
||||
max_name_length: int = 32,
|
||||
suffix_length: int = 6,
|
||||
dup_warn_filter: Callable[[str,], bool] = None, # If returns False, don't warn about this name
|
||||
dup_warn_filter: Callable[[str], bool] = None, # If returns False, don't warn about this name
|
||||
) -> None:
|
||||
used_names = []
|
||||
for pat in patterns:
|
||||
sanitized_name = re.compile('[^A-Za-z0-9_\?\$]').sub('_', pat.name)
|
||||
sanitized_name = re.compile(r'[^A-Za-z0-9_\?\$]').sub('_', pat.name)
|
||||
|
||||
i = 0
|
||||
suffixed_name = sanitized_name
|
||||
|
|
@ -374,15 +370,15 @@ def disambiguate_pattern_names(patterns: Sequence[Pattern],
|
|||
logger.warning(f'Empty pattern name saved as "{suffixed_name}"')
|
||||
elif suffixed_name != sanitized_name:
|
||||
if dup_warn_filter is None or dup_warn_filter(pat.name):
|
||||
logger.warning(f'Pattern name "{pat.name}" ({sanitized_name}) appears multiple times;\n' +
|
||||
f' renaming to "{suffixed_name}"')
|
||||
logger.warning(f'Pattern name "{pat.name}" ({sanitized_name}) appears multiple times;\n'
|
||||
+ f' renaming to "{suffixed_name}"')
|
||||
|
||||
if len(suffixed_name) == 0:
|
||||
# Should never happen since zero-length names are replaced
|
||||
raise PatternError(f'Zero-length name after sanitize,\n originally "{pat.name}"')
|
||||
if len(suffixed_name) > max_name_length:
|
||||
raise PatternError(f'Pattern name "{suffixed_name!r}" length > {max_name_length} after encode,\n' +
|
||||
f' originally "{pat.name}"')
|
||||
raise PatternError(f'Pattern name "{suffixed_name!r}" length > {max_name_length} after encode,\n'
|
||||
+ f' originally "{pat.name}"')
|
||||
|
||||
pat.name = suffixed_name
|
||||
used_names.append(suffixed_name)
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ Notes:
|
|||
* ELFLAGS are not supported
|
||||
* GDS does not support library- or structure-level annotations
|
||||
"""
|
||||
from typing import List, Any, Dict, Tuple, Callable, Union, Sequence, Iterable, Optional
|
||||
from typing import Sequence, Mapping
|
||||
from typing import List, Any, Dict, Tuple, Callable, Union, Iterable, Optional
|
||||
from typing import Sequence
|
||||
import re
|
||||
import io
|
||||
import copy
|
||||
|
|
@ -34,25 +34,23 @@ import gdsii.library
|
|||
import gdsii.structure
|
||||
import gdsii.elements
|
||||
|
||||
from .utils import mangle_name, make_dose_table, dose2dtype, dtype2dose, clean_pattern_vertices
|
||||
from .utils import is_gzipped
|
||||
from .utils import clean_pattern_vertices, is_gzipped
|
||||
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, annotations_t
|
||||
from ..utils import get_bit, set_bit, layer_t, normalize_mirror, annotations_t
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
path_cap_map = {
|
||||
None: Path.Cap.Flush,
|
||||
0: Path.Cap.Flush,
|
||||
1: Path.Cap.Circle,
|
||||
2: Path.Cap.Square,
|
||||
4: Path.Cap.SquareCustom,
|
||||
}
|
||||
None: Path.Cap.Flush,
|
||||
0: Path.Cap.Flush,
|
||||
1: Path.Cap.Circle,
|
||||
2: Path.Cap.Square,
|
||||
4: Path.Cap.SquareCustom,
|
||||
}
|
||||
|
||||
|
||||
def build(patterns: Union[Pattern, Sequence[Pattern]],
|
||||
|
|
@ -262,8 +260,7 @@ def read(stream: io.BufferedIOBase,
|
|||
string=element.string.decode('ASCII'))
|
||||
pat.labels.append(label)
|
||||
|
||||
elif (isinstance(element, gdsii.elements.SRef) or
|
||||
isinstance(element, gdsii.elements.ARef)):
|
||||
elif isinstance(element, (gdsii.elements.SRef, gdsii.elements.ARef)):
|
||||
pat.subpatterns.append(_ref_to_subpat(element))
|
||||
|
||||
if clean_vertices:
|
||||
|
|
@ -358,7 +355,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),
|
||||
'annotations': _properties_to_annotations(element.properties),
|
||||
'raw': raw_mode,
|
||||
}
|
||||
|
||||
|
|
@ -376,7 +373,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),
|
||||
'annotations': _properties_to_annotations(element.properties),
|
||||
'raw': raw_mode,
|
||||
}
|
||||
return Polygon(**args)
|
||||
|
|
@ -398,14 +395,14 @@ def _subpatterns_to_refs(subpatterns: List[SubPattern]
|
|||
ref: Union[gdsii.elements.SRef, gdsii.elements.ARef]
|
||||
if isinstance(rep, Grid):
|
||||
xy = numpy.array(subpat.offset) + [
|
||||
[0, 0],
|
||||
rep.a_vector * rep.a_count,
|
||||
rep.b_vector * rep.b_count,
|
||||
]
|
||||
[0, 0],
|
||||
rep.a_vector * rep.a_count,
|
||||
rep.b_vector * rep.b_count,
|
||||
]
|
||||
ref = gdsii.elements.ARef(struct_name=encoded_name,
|
||||
xy=numpy.round(xy).astype(int),
|
||||
cols=numpy.round(rep.a_count).astype(int),
|
||||
rows=numpy.round(rep.b_count).astype(int))
|
||||
xy=numpy.round(xy).astype(int),
|
||||
cols=numpy.round(rep.a_count).astype(int),
|
||||
rows=numpy.round(rep.b_count).astype(int))
|
||||
new_refs = [ref]
|
||||
elif rep is None:
|
||||
ref = gdsii.elements.SRef(struct_name=encoded_name,
|
||||
|
|
@ -437,7 +434,7 @@ def _annotations_to_properties(annotations: annotations_t, max_len: int = 126) -
|
|||
for key, vals in annotations.items():
|
||||
try:
|
||||
i = int(key)
|
||||
except:
|
||||
except ValueError:
|
||||
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])')
|
||||
|
|
@ -464,7 +461,7 @@ def _shapes_to_elements(shapes: List[Shape],
|
|||
if isinstance(shape, Path) and not polygonize_paths:
|
||||
xy = numpy.round(shape.vertices + shape.offset).astype(int)
|
||||
width = numpy.round(shape.width).astype(int)
|
||||
path_type = next(k for k, v in path_cap_map.items() if v == shape.cap) #reverse lookup
|
||||
path_type = next(k for k, v in path_cap_map.items() if v == shape.cap) # reverse lookup
|
||||
path = gdsii.elements.Path(layer=layer,
|
||||
data_type=data_type,
|
||||
xy=xy)
|
||||
|
|
@ -502,7 +499,7 @@ def _labels_to_texts(labels: List[Label]) -> List[gdsii.elements.Text]:
|
|||
def disambiguate_pattern_names(patterns: Sequence[Pattern],
|
||||
max_name_length: int = 32,
|
||||
suffix_length: int = 6,
|
||||
dup_warn_filter: Optional[Callable[[str,], bool]] = None,
|
||||
dup_warn_filter: Optional[Callable[[str], bool]] = None,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
|
|
@ -519,13 +516,13 @@ def disambiguate_pattern_names(patterns: Sequence[Pattern],
|
|||
# Shorten names which already exceed max-length
|
||||
if len(pat.name) > max_name_length:
|
||||
shortened_name = pat.name[:max_name_length - suffix_length]
|
||||
logger.warning(f'Pattern name "{pat.name}" is too long ({len(pat.name)}/{max_name_length} chars),\n' +
|
||||
f' shortening to "{shortened_name}" before generating suffix')
|
||||
logger.warning(f'Pattern name "{pat.name}" is too long ({len(pat.name)}/{max_name_length} chars),\n'
|
||||
+ f' shortening to "{shortened_name}" before generating suffix')
|
||||
else:
|
||||
shortened_name = pat.name
|
||||
|
||||
# Remove invalid characters
|
||||
sanitized_name = re.compile('[^A-Za-z0-9_\?\$]').sub('_', shortened_name)
|
||||
sanitized_name = re.compile(r'[^A-Za-z0-9_\?\$]').sub('_', shortened_name)
|
||||
|
||||
# Add a suffix that makes the name unique
|
||||
i = 0
|
||||
|
|
@ -540,8 +537,8 @@ def disambiguate_pattern_names(patterns: Sequence[Pattern],
|
|||
logger.warning(f'Empty pattern name saved as "{suffixed_name}"')
|
||||
elif suffixed_name != sanitized_name:
|
||||
if dup_warn_filter is None or dup_warn_filter(pat.name):
|
||||
logger.warning(f'Pattern name "{pat.name}" ({sanitized_name}) appears multiple times;\n' +
|
||||
f' renaming to "{suffixed_name}"')
|
||||
logger.warning(f'Pattern name "{pat.name}" ({sanitized_name}) appears multiple times;\n'
|
||||
+ f' renaming to "{suffixed_name}"')
|
||||
|
||||
# Encode into a byte-string and perform some final checks
|
||||
encoded_name = suffixed_name.encode('ASCII')
|
||||
|
|
@ -549,8 +546,8 @@ def disambiguate_pattern_names(patterns: Sequence[Pattern],
|
|||
# Should never happen since zero-length names are replaced
|
||||
raise PatternError(f'Zero-length name after sanitize+encode,\n originally "{pat.name}"')
|
||||
if len(encoded_name) > max_name_length:
|
||||
raise PatternError(f'Pattern name "{encoded_name!r}" length > {max_name_length} after encode,\n' +
|
||||
f' originally "{pat.name}"')
|
||||
raise PatternError(f'Pattern name "{encoded_name!r}" length > {max_name_length} after encode,\n'
|
||||
+ f' originally "{pat.name}"')
|
||||
|
||||
pat.name = suffixed_name
|
||||
used_names.append(suffixed_name)
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ Notes:
|
|||
* GDS does not support library- or structure-level annotations
|
||||
* Creation/modification/access times are set to 1900-01-01 for reproducibility.
|
||||
"""
|
||||
from typing import List, Any, Dict, Tuple, Callable, Union, Sequence, Iterable, Optional
|
||||
from typing import Sequence, Mapping, BinaryIO
|
||||
from typing import List, Any, Dict, Tuple, Callable, Union, Iterable, Optional
|
||||
from typing import Sequence, BinaryIO
|
||||
import re
|
||||
import io
|
||||
import mmap
|
||||
|
|
@ -29,29 +29,27 @@ import struct
|
|||
import logging
|
||||
import pathlib
|
||||
import gzip
|
||||
from itertools import chain
|
||||
|
||||
import numpy # type: ignore
|
||||
import klamath
|
||||
from klamath import records
|
||||
|
||||
from .utils import mangle_name, make_dose_table, dose2dtype, dtype2dose, is_gzipped
|
||||
from .utils import is_gzipped
|
||||
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, annotations_t
|
||||
from ..utils import layer_t, normalize_mirror, annotations_t
|
||||
from ..library import Library
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
path_cap_map = {
|
||||
0: Path.Cap.Flush,
|
||||
1: Path.Cap.Circle,
|
||||
2: Path.Cap.Square,
|
||||
4: Path.Cap.SquareCustom,
|
||||
}
|
||||
0: Path.Cap.Flush,
|
||||
1: Path.Cap.Circle,
|
||||
2: Path.Cap.Square,
|
||||
4: Path.Cap.SquareCustom,
|
||||
}
|
||||
|
||||
|
||||
def write(patterns: Union[Pattern, Sequence[Pattern]],
|
||||
|
|
@ -144,15 +142,15 @@ def writefile(patterns: Union[Sequence[Pattern], Pattern],
|
|||
**kwargs,
|
||||
) -> None:
|
||||
"""
|
||||
Wrapper for `masque.file.gdsii.write()` that takes a filename or path instead of a stream.
|
||||
Wrapper for `write()` that takes a filename or path instead of a stream.
|
||||
|
||||
Will automatically compress the file if it has a .gz suffix.
|
||||
|
||||
Args:
|
||||
patterns: `Pattern` or list of patterns to save
|
||||
filename: Filename to save to.
|
||||
*args: passed to `masque.file.gdsii.write`
|
||||
**kwargs: passed to `masque.file.gdsii.write`
|
||||
*args: passed to `write()`
|
||||
**kwargs: passed to `write()`
|
||||
"""
|
||||
path = pathlib.Path(filename)
|
||||
if path.suffix == '.gz':
|
||||
|
|
@ -169,14 +167,14 @@ def readfile(filename: Union[str, pathlib.Path],
|
|||
**kwargs,
|
||||
) -> Tuple[Dict[str, Pattern], Dict[str, Any]]:
|
||||
"""
|
||||
Wrapper for `masque.file.gdsii.read()` that takes a filename or path instead of a stream.
|
||||
Wrapper for `read()` that takes a filename or path instead of a stream.
|
||||
|
||||
Will automatically decompress gzipped files.
|
||||
|
||||
Args:
|
||||
filename: Filename to save to.
|
||||
*args: passed to `masque.file.gdsii.read`
|
||||
**kwargs: passed to `masque.file.gdsii.read`
|
||||
*args: passed to `read()`
|
||||
**kwargs: passed to `read()`
|
||||
"""
|
||||
path = pathlib.Path(filename)
|
||||
if is_gzipped(path):
|
||||
|
|
@ -185,7 +183,7 @@ def readfile(filename: Union[str, pathlib.Path],
|
|||
open_func = open
|
||||
|
||||
with io.BufferedReader(open_func(path, mode='rb')) as stream:
|
||||
results = read(stream)#, *args, **kwargs)
|
||||
results = read(stream, *args, **kwargs)
|
||||
return results
|
||||
|
||||
|
||||
|
|
@ -216,7 +214,7 @@ def read(stream: BinaryIO,
|
|||
found_struct = records.BGNSTR.skip_past(stream)
|
||||
while found_struct:
|
||||
name = records.STRNAME.skip_and_read(stream)
|
||||
pat = read_elements(stream, name=name.decode('ASCII'))
|
||||
pat = read_elements(stream, name=name.decode('ASCII'), raw_mode=raw_mode)
|
||||
patterns.append(pat)
|
||||
found_struct = records.BGNSTR.skip_past(stream)
|
||||
|
||||
|
|
@ -368,10 +366,10 @@ def _subpatterns_to_refs(subpatterns: List[SubPattern]
|
|||
|
||||
if isinstance(rep, Grid):
|
||||
xy = numpy.array(subpat.offset) + [
|
||||
[0, 0],
|
||||
rep.a_vector * rep.a_count,
|
||||
rep.b_vector * rep.b_count,
|
||||
]
|
||||
[0, 0],
|
||||
rep.a_vector * rep.a_count,
|
||||
rep.b_vector * rep.b_count,
|
||||
]
|
||||
aref = klamath.library.Reference(struct_name=encoded_name,
|
||||
xy=numpy.round(xy).astype(int),
|
||||
colrow=(numpy.round(rep.a_count), numpy.round(rep.b_count)),
|
||||
|
|
@ -412,7 +410,7 @@ def _annotations_to_properties(annotations: annotations_t, max_len: int = 126) -
|
|||
for key, vals in annotations.items():
|
||||
try:
|
||||
i = int(key)
|
||||
except:
|
||||
except ValueError:
|
||||
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])')
|
||||
|
|
@ -439,7 +437,7 @@ def _shapes_to_elements(shapes: List[Shape],
|
|||
if isinstance(shape, Path) and not polygonize_paths:
|
||||
xy = numpy.round(shape.vertices + shape.offset).astype(int)
|
||||
width = numpy.round(shape.width).astype(int)
|
||||
path_type = next(k for k, v in path_cap_map.items() if v == shape.cap) #reverse lookup
|
||||
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:
|
||||
|
|
@ -455,13 +453,13 @@ def _shapes_to_elements(shapes: List[Shape],
|
|||
properties=properties)
|
||||
elements.append(path)
|
||||
elif isinstance(shape, Polygon):
|
||||
polygon = shape
|
||||
xy_open = numpy.round(polygon.vertices + polygon.offset).astype(int)
|
||||
xy_closed = numpy.vstack((xy_open, xy_open[0, :]))
|
||||
boundary = klamath.elements.Boundary(layer=(layer, data_type),
|
||||
xy=xy_closed,
|
||||
properties=properties)
|
||||
elements.append(boundary)
|
||||
polygon = shape
|
||||
xy_open = numpy.round(polygon.vertices + polygon.offset).astype(int)
|
||||
xy_closed = numpy.vstack((xy_open, xy_open[0, :]))
|
||||
boundary = klamath.elements.Boundary(layer=(layer, data_type),
|
||||
xy=xy_closed,
|
||||
properties=properties)
|
||||
elements.append(boundary)
|
||||
else:
|
||||
for polygon in shape.to_polygons():
|
||||
xy_open = numpy.round(polygon.vertices + polygon.offset).astype(int)
|
||||
|
|
@ -483,7 +481,7 @@ def _labels_to_texts(labels: List[Label]) -> List[klamath.elements.Text]:
|
|||
xy=xy,
|
||||
string=label.string.encode('ASCII'),
|
||||
properties=properties,
|
||||
presentation=0, #TODO maybe set some of these?
|
||||
presentation=0, # TODO maybe set some of these?
|
||||
angle_deg=0,
|
||||
invert_y=False,
|
||||
width=0,
|
||||
|
|
@ -496,7 +494,7 @@ def _labels_to_texts(labels: List[Label]) -> List[klamath.elements.Text]:
|
|||
def disambiguate_pattern_names(patterns: Sequence[Pattern],
|
||||
max_name_length: int = 32,
|
||||
suffix_length: int = 6,
|
||||
dup_warn_filter: Optional[Callable[[str,], bool]] = None,
|
||||
dup_warn_filter: Optional[Callable[[str], bool]] = None,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
|
|
@ -513,13 +511,13 @@ def disambiguate_pattern_names(patterns: Sequence[Pattern],
|
|||
# Shorten names which already exceed max-length
|
||||
if len(pat.name) > max_name_length:
|
||||
shortened_name = pat.name[:max_name_length - suffix_length]
|
||||
logger.warning(f'Pattern name "{pat.name}" is too long ({len(pat.name)}/{max_name_length} chars),\n' +
|
||||
f' shortening to "{shortened_name}" before generating suffix')
|
||||
logger.warning(f'Pattern name "{pat.name}" is too long ({len(pat.name)}/{max_name_length} chars),\n'
|
||||
+ f' shortening to "{shortened_name}" before generating suffix')
|
||||
else:
|
||||
shortened_name = pat.name
|
||||
|
||||
# Remove invalid characters
|
||||
sanitized_name = re.compile('[^A-Za-z0-9_\?\$]').sub('_', shortened_name)
|
||||
sanitized_name = re.compile(r'[^A-Za-z0-9_\?\$]').sub('_', shortened_name)
|
||||
|
||||
# Add a suffix that makes the name unique
|
||||
i = 0
|
||||
|
|
@ -534,8 +532,8 @@ def disambiguate_pattern_names(patterns: Sequence[Pattern],
|
|||
logger.warning(f'Empty pattern name saved as "{suffixed_name}"')
|
||||
elif suffixed_name != sanitized_name:
|
||||
if dup_warn_filter is None or dup_warn_filter(pat.name):
|
||||
logger.warning(f'Pattern name "{pat.name}" ({sanitized_name}) appears multiple times;\n' +
|
||||
f' renaming to "{suffixed_name}"')
|
||||
logger.warning(f'Pattern name "{pat.name}" ({sanitized_name}) appears multiple times;\n'
|
||||
+ f' renaming to "{suffixed_name}"')
|
||||
|
||||
# Encode into a byte-string and perform some final checks
|
||||
encoded_name = suffixed_name.encode('ASCII')
|
||||
|
|
@ -543,8 +541,8 @@ def disambiguate_pattern_names(patterns: Sequence[Pattern],
|
|||
# Should never happen since zero-length names are replaced
|
||||
raise PatternError(f'Zero-length name after sanitize+encode,\n originally "{pat.name}"')
|
||||
if len(encoded_name) > max_name_length:
|
||||
raise PatternError(f'Pattern name "{encoded_name!r}" length > {max_name_length} after encode,\n' +
|
||||
f' originally "{pat.name}"')
|
||||
raise PatternError(f'Pattern name "{encoded_name!r}" length > {max_name_length} after encode,\n'
|
||||
+ f' originally "{pat.name}"')
|
||||
|
||||
pat.name = suffixed_name
|
||||
used_names.append(suffixed_name)
|
||||
|
|
@ -576,7 +574,8 @@ def load_library(stream: BinaryIO,
|
|||
Additional library info (dict, same format as from `read`).
|
||||
"""
|
||||
if is_secondary is None:
|
||||
is_secondary = lambda k: False
|
||||
def is_secondary(k: str):
|
||||
return False
|
||||
|
||||
stream.seek(0)
|
||||
library_info = _read_header(stream)
|
||||
|
|
@ -592,7 +591,7 @@ def load_library(stream: BinaryIO,
|
|||
|
||||
lib.set_value(name, tag, mkstruct, secondary=is_secondary(name))
|
||||
|
||||
return lib
|
||||
return lib, library_info
|
||||
|
||||
|
||||
def load_libraryfile(filename: Union[str, pathlib.Path],
|
||||
|
|
|
|||
|
|
@ -22,17 +22,15 @@ import pathlib
|
|||
import gzip
|
||||
|
||||
import numpy # type: ignore
|
||||
from numpy import pi
|
||||
import fatamorgana
|
||||
import fatamorgana.records as fatrec
|
||||
from fatamorgana.basic import PathExtensionScheme, AString, NString, PropStringReference
|
||||
|
||||
from .utils import mangle_name, make_dose_table, clean_pattern_vertices, is_gzipped
|
||||
from .utils import clean_pattern_vertices, is_gzipped
|
||||
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, annotations_t
|
||||
from ..utils import layer_t, normalize_mirror, annotations_t
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -42,10 +40,10 @@ logger.warning('OASIS support is experimental and mostly untested!')
|
|||
|
||||
|
||||
path_cap_map = {
|
||||
PathExtensionScheme.Flush: Path.Cap.Flush,
|
||||
PathExtensionScheme.HalfWidth: Path.Cap.Square,
|
||||
PathExtensionScheme.Arbitrary: Path.Cap.SquareCustom,
|
||||
}
|
||||
PathExtensionScheme.Flush: Path.Cap.Flush,
|
||||
PathExtensionScheme.HalfWidth: Path.Cap.Square,
|
||||
PathExtensionScheme.Arbitrary: Path.Cap.SquareCustom,
|
||||
}
|
||||
|
||||
#TODO implement more shape types?
|
||||
|
||||
|
|
@ -120,11 +118,11 @@ def build(patterns: Union[Pattern, Sequence[Pattern]],
|
|||
for name, layer_num in layer_map.items():
|
||||
layer, data_type = _mlayer2oas(layer_num)
|
||||
lib.layers += [
|
||||
fatrec.LayerName(nstring=name,
|
||||
layer_interval=(layer, layer),
|
||||
type_interval=(data_type, data_type),
|
||||
is_textlayer=tt)
|
||||
for tt in (True, False)]
|
||||
fatrec.LayerName(nstring=name,
|
||||
layer_interval=(layer, layer),
|
||||
type_interval=(data_type, data_type),
|
||||
is_textlayer=tt)
|
||||
for tt in (True, False)]
|
||||
|
||||
def layer2oas(mlayer: layer_t) -> Tuple[int, int]:
|
||||
assert(layer_map is not None)
|
||||
|
|
@ -252,9 +250,9 @@ def read(stream: io.BufferedIOBase,
|
|||
lib = fatamorgana.OasisLayout.read(stream)
|
||||
|
||||
library_info: Dict[str, Any] = {
|
||||
'units_per_micrometer': lib.unit,
|
||||
'annotations': properties_to_annotations(lib.properties, lib.propnames, lib.propstrings),
|
||||
}
|
||||
'units_per_micrometer': lib.unit,
|
||||
'annotations': properties_to_annotations(lib.properties, lib.propnames, lib.propstrings),
|
||||
}
|
||||
|
||||
layer_map = {}
|
||||
for layer_name in lib.layers:
|
||||
|
|
@ -296,7 +294,7 @@ def read(stream: io.BufferedIOBase,
|
|||
cap_start = path_cap_map[element.get_extension_start()[0]]
|
||||
cap_end = path_cap_map[element.get_extension_end()[0]]
|
||||
if cap_start != cap_end:
|
||||
raise Exception('masque does not support multiple cap types on a single path.') #TODO handle multiple cap types
|
||||
raise Exception('masque does not support multiple cap types on a single path.') # TODO handle multiple cap types
|
||||
cap = cap_start
|
||||
|
||||
path_args: Dict[str, Any] = {}
|
||||
|
|
@ -472,7 +470,7 @@ def _mlayer2oas(mlayer: layer_t) -> Tuple[int, int]:
|
|||
data_type = 0
|
||||
else:
|
||||
raise PatternError(f'Invalid layer for OASIS: {layer}. Note that OASIS layers cannot be '
|
||||
'strings unless a layer map is provided.')
|
||||
f'strings unless a layer map is provided.')
|
||||
return layer, data_type
|
||||
|
||||
|
||||
|
|
@ -490,7 +488,7 @@ def _placement_to_subpat(placement: fatrec.Placement, lib: fatamorgana.OasisLayo
|
|||
subpat = SubPattern(offset=xy,
|
||||
pattern=None,
|
||||
mirrored=(placement.flip, False),
|
||||
rotation=float(placement.angle * pi/180),
|
||||
rotation=numpy.deg2rad(placement.angle),
|
||||
scale=float(mag),
|
||||
identifier=(name,),
|
||||
repetition=repetition_fata2masq(placement.repetition),
|
||||
|
|
@ -512,14 +510,14 @@ def _subpatterns_to_placements(subpatterns: List[SubPattern]
|
|||
offset = numpy.round(subpat.offset + rep_offset).astype(int)
|
||||
angle = numpy.rad2deg(subpat.rotation + extra_angle) % 360
|
||||
ref = fatrec.Placement(
|
||||
name=subpat.pattern.name,
|
||||
flip=mirror_across_x,
|
||||
angle=angle,
|
||||
magnification=subpat.scale,
|
||||
properties=annotations_to_properties(subpat.annotations),
|
||||
x=offset[0],
|
||||
y=offset[1],
|
||||
repetition=frep)
|
||||
name=subpat.pattern.name,
|
||||
flip=mirror_across_x,
|
||||
angle=angle,
|
||||
magnification=subpat.scale,
|
||||
properties=annotations_to_properties(subpat.annotations),
|
||||
x=offset[0],
|
||||
y=offset[1],
|
||||
repetition=frep)
|
||||
|
||||
refs.append(ref)
|
||||
return refs
|
||||
|
|
@ -549,7 +547,7 @@ def _shapes_to_elements(shapes: List[Shape],
|
|||
xy = numpy.round(shape.offset + shape.vertices[0] + rep_offset).astype(int)
|
||||
deltas = numpy.round(numpy.diff(shape.vertices, axis=0)).astype(int)
|
||||
half_width = numpy.round(shape.width / 2).astype(int)
|
||||
path_type = next(k for k, v in path_cap_map.items() if v == shape.cap) #reverse lookup
|
||||
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,
|
||||
|
|
@ -558,7 +556,7 @@ def _shapes_to_elements(shapes: List[Shape],
|
|||
half_width=half_width,
|
||||
x=xy[0],
|
||||
y=xy[1],
|
||||
extension_start=extension_start, #TODO implement multiple cap types?
|
||||
extension_start=extension_start, # TODO implement multiple cap types?
|
||||
extension_end=extension_end,
|
||||
properties=properties,
|
||||
repetition=repetition,
|
||||
|
|
@ -598,11 +596,11 @@ def _labels_to_texts(labels: List[Label],
|
|||
|
||||
|
||||
def disambiguate_pattern_names(patterns,
|
||||
dup_warn_filter: Callable[[str,], bool] = None, # If returns False, don't warn about this name
|
||||
dup_warn_filter: Callable[[str], bool] = None, # If returns False, don't warn about this name
|
||||
):
|
||||
used_names = []
|
||||
for pat in patterns:
|
||||
sanitized_name = re.compile('[^A-Za-z0-9_\?\$]').sub('_', pat.name)
|
||||
sanitized_name = re.compile(r'[^A-Za-z0-9_\?\$]').sub('_', pat.name)
|
||||
|
||||
i = 0
|
||||
suffixed_name = sanitized_name
|
||||
|
|
@ -616,8 +614,8 @@ def disambiguate_pattern_names(patterns,
|
|||
logger.warning(f'Empty pattern name saved as "{suffixed_name}"')
|
||||
elif suffixed_name != sanitized_name:
|
||||
if dup_warn_filter is None or dup_warn_filter(pat.name):
|
||||
logger.warning(f'Pattern name "{pat.name}" ({sanitized_name}) appears multiple times;\n' +
|
||||
f' renaming to "{suffixed_name}"')
|
||||
logger.warning(f'Pattern name "{pat.name}" ({sanitized_name}) appears multiple times;\n'
|
||||
+ f' renaming to "{suffixed_name}"')
|
||||
|
||||
if len(suffixed_name) == 0:
|
||||
# Should never happen since zero-length names are replaced
|
||||
|
|
@ -653,10 +651,10 @@ def repetition_masq2fata(rep: Optional[Repetition]
|
|||
frep: Union[fatamorgana.GridRepetition, fatamorgana.ArbitraryRepetition, None]
|
||||
if isinstance(rep, Grid):
|
||||
frep = fatamorgana.GridRepetition(
|
||||
a_vector=numpy.round(rep.a_vector).astype(int),
|
||||
b_vector=numpy.round(rep.b_vector).astype(int),
|
||||
a_count=numpy.round(rep.a_count).astype(int),
|
||||
b_count=numpy.round(rep.b_count).astype(int))
|
||||
a_vector=numpy.round(rep.a_vector).astype(int),
|
||||
b_vector=numpy.round(rep.b_vector).astype(int),
|
||||
a_count=numpy.round(rep.a_count).astype(int),
|
||||
b_count=numpy.round(rep.b_count).astype(int))
|
||||
offset = (0, 0)
|
||||
elif isinstance(rep, Arbitrary):
|
||||
diffs = numpy.diff(rep.displacements, axis=0)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ from .. import Pattern
|
|||
|
||||
def writefile(pattern: Pattern,
|
||||
filename: str,
|
||||
custom_attributes: bool=False):
|
||||
custom_attributes: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
Write a Pattern to an SVG file, by first calling .polygonize() on it
|
||||
to change the shapes into polygons, and then writing patterns as SVG
|
||||
|
|
|
|||
|
|
@ -4,14 +4,13 @@ Helper functions for file reading and writing
|
|||
from typing import Set, Tuple, List
|
||||
import re
|
||||
import copy
|
||||
import gzip
|
||||
import pathlib
|
||||
|
||||
from .. import Pattern, PatternError
|
||||
from ..shapes import Polygon, Path
|
||||
|
||||
|
||||
def mangle_name(pattern: Pattern, dose_multiplier: float=1.0) -> str:
|
||||
def mangle_name(pattern: Pattern, dose_multiplier: float = 1.0) -> str:
|
||||
"""
|
||||
Create a name using `pattern.name`, `id(pattern)`, and the dose multiplier.
|
||||
|
||||
|
|
@ -22,7 +21,7 @@ def mangle_name(pattern: Pattern, dose_multiplier: float=1.0) -> str:
|
|||
Returns:
|
||||
Mangled name.
|
||||
"""
|
||||
expression = re.compile('[^A-Za-z0-9_\?\$]')
|
||||
expression = re.compile(r'[^A-Za-z0-9_\?\$]')
|
||||
full_name = '{}_{}_{}'.format(pattern.name, dose_multiplier, id(pattern))
|
||||
sanitized_name = expression.sub('_', full_name)
|
||||
return sanitized_name
|
||||
|
|
@ -52,7 +51,7 @@ def clean_pattern_vertices(pat: Pattern) -> Pattern:
|
|||
return pat
|
||||
|
||||
|
||||
def make_dose_table(patterns: List[Pattern], dose_multiplier: float=1.0) -> Set[Tuple[int, float]]:
|
||||
def make_dose_table(patterns: List[Pattern], dose_multiplier: float = 1.0) -> Set[Tuple[int, float]]:
|
||||
"""
|
||||
Create a set containing `(id(pat), written_dose)` for each pattern (including subpatterns)
|
||||
|
||||
|
|
@ -144,14 +143,14 @@ def dose2dtype(patterns: List[Pattern],
|
|||
|
||||
# Create a new pattern for each non-1-dose entry in the dose table
|
||||
# and update the shapes to reflect their new dose
|
||||
new_pats = {} # (id, dose) -> new_pattern mapping
|
||||
new_pats = {} # (id, dose) -> new_pattern mapping
|
||||
for pat_id, pat_dose in sd_table:
|
||||
if pat_dose == 1:
|
||||
new_pats[(pat_id, pat_dose)] = patterns_by_id[pat_id]
|
||||
continue
|
||||
|
||||
old_pat = patterns_by_id[pat_id]
|
||||
pat = old_pat.copy() # keep old subpatterns
|
||||
pat = old_pat.copy() # keep old subpatterns
|
||||
pat.shapes = copy.deepcopy(old_pat.shapes)
|
||||
pat.labels = copy.deepcopy(old_pat.labels)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue