add support for annotations
and other fixes
This commit is contained in:
parent
5d83e0e5c0
commit
49a3b4e322
@ -17,7 +17,8 @@ def main():
|
|||||||
pat.shapes.append(shapes.Arc(
|
pat.shapes.append(shapes.Arc(
|
||||||
radii=(rmin, rmin),
|
radii=(rmin, rmin),
|
||||||
width=0.1,
|
width=0.1,
|
||||||
angles=(0*-numpy.pi/4, numpy.pi/4)
|
angles=(0*-numpy.pi/4, numpy.pi/4),
|
||||||
|
annotations={'1': ['blah']},
|
||||||
))
|
))
|
||||||
|
|
||||||
pat.scale_by(1000)
|
pat.scale_by(1000)
|
||||||
@ -27,7 +28,7 @@ def main():
|
|||||||
|
|
||||||
pat3 = Pattern('sref_test')
|
pat3 = Pattern('sref_test')
|
||||||
pat3.subpatterns = [
|
pat3.subpatterns = [
|
||||||
SubPattern(pat, offset=(1e5, 3e5)),
|
SubPattern(pat, offset=(1e5, 3e5), annotations={'4': ['Hello I am the base subpattern']}),
|
||||||
SubPattern(pat, offset=(2e5, 3e5), rotation=pi/3),
|
SubPattern(pat, offset=(2e5, 3e5), rotation=pi/3),
|
||||||
SubPattern(pat, offset=(3e5, 3e5), rotation=pi/2),
|
SubPattern(pat, offset=(3e5, 3e5), rotation=pi/2),
|
||||||
SubPattern(pat, offset=(4e5, 3e5), rotation=pi),
|
SubPattern(pat, offset=(4e5, 3e5), rotation=pi),
|
||||||
|
@ -31,7 +31,7 @@ from .shapes import Shape
|
|||||||
from .label import Label
|
from .label import Label
|
||||||
from .subpattern import SubPattern
|
from .subpattern import SubPattern
|
||||||
from .pattern import Pattern
|
from .pattern import Pattern
|
||||||
from .utils import layer_t
|
from .utils import layer_t, annotations_t
|
||||||
|
|
||||||
|
|
||||||
__author__ = 'Jan Petykiewicz'
|
__author__ = 'Jan Petykiewicz'
|
||||||
|
@ -10,10 +10,10 @@ import struct
|
|||||||
import logging
|
import logging
|
||||||
import pathlib
|
import pathlib
|
||||||
import gzip
|
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 .utils import mangle_name, make_dose_table
|
||||||
from .. import Pattern, SubPattern, PatternError, Label, Shape
|
from .. import Pattern, SubPattern, PatternError, Label, Shape
|
||||||
@ -264,12 +264,11 @@ def _read_block(block, clean_vertices):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if 'column_count' in attr:
|
if 'column_count' in attr:
|
||||||
args['a_vector'] = (attr['column_spacing'], 0)
|
args['repetition'] = Grid(
|
||||||
args['b_vector'] = (0, attr['row_spacing'])
|
a_vector=(attr['column_spacing'], 0),
|
||||||
args['a_count'] = attr['column_count']
|
b_vector=(0, attr['row_spacing']),
|
||||||
args['b_count'] = attr['row_count']
|
a_count=attr['column_count'],
|
||||||
pat.subpatterns.append(GridRepetition(**args))
|
b_count=attr['row_count'])
|
||||||
else:
|
|
||||||
pat.subpatterns.append(SubPattern(**args))
|
pat.subpatterns.append(SubPattern(**args))
|
||||||
else:
|
else:
|
||||||
logger.warning(f'Ignoring DXF element {element.dxftype()} (not implemented).')
|
logger.warning(f'Ignoring DXF element {element.dxftype()} (not implemented).')
|
||||||
|
@ -11,17 +11,18 @@ Note that GDSII references follow the same convention as `masque`,
|
|||||||
Scaling, rotation, and mirroring apply to individual instances, not grid
|
Scaling, rotation, and mirroring apply to individual instances, not grid
|
||||||
vectors or offsets.
|
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 re
|
||||||
import io
|
import io
|
||||||
import copy
|
import copy
|
||||||
import numpy
|
|
||||||
import base64
|
import base64
|
||||||
import struct
|
import struct
|
||||||
import logging
|
import logging
|
||||||
import pathlib
|
import pathlib
|
||||||
import gzip
|
import gzip
|
||||||
|
|
||||||
|
import numpy # type: ignore
|
||||||
# python-gdsii
|
# python-gdsii
|
||||||
import gdsii.library
|
import gdsii.library
|
||||||
import gdsii.structure
|
import gdsii.structure
|
||||||
@ -32,7 +33,7 @@ from .. import Pattern, SubPattern, PatternError, Label, Shape
|
|||||||
from ..shapes import Polygon, Path
|
from ..shapes import Polygon, Path
|
||||||
from ..repetition import Grid
|
from ..repetition import Grid
|
||||||
from ..utils import rotation_matrix_2d, get_bit, set_bit, vector2, is_scalar, layer_t
|
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
|
#TODO absolute positioning
|
||||||
|
|
||||||
@ -99,6 +100,7 @@ def build(patterns: Union[Pattern, List[Pattern]],
|
|||||||
|
|
||||||
if disambiguate_func is None:
|
if disambiguate_func is None:
|
||||||
disambiguate_func = disambiguate_pattern_names
|
disambiguate_func = disambiguate_pattern_names
|
||||||
|
assert(disambiguate_func is not None) # placate mypy
|
||||||
|
|
||||||
if not modify_originals:
|
if not modify_originals:
|
||||||
patterns = [p.deepunlock() for p in copy.deepcopy(patterns)]
|
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'))
|
structure = gdsii.structure.Structure(name=pat.name.encode('ASCII'))
|
||||||
lib.append(structure)
|
lib.append(structure)
|
||||||
|
|
||||||
|
structure.properties = _annotations_to_properties(pat.annotations, 512)
|
||||||
|
|
||||||
structure += _shapes_to_elements(pat.shapes)
|
structure += _shapes_to_elements(pat.shapes)
|
||||||
structure += _labels_to_texts(pat.labels)
|
structure += _labels_to_texts(pat.labels)
|
||||||
structure += _subpatterns_to_refs(pat.subpatterns)
|
structure += _subpatterns_to_refs(pat.subpatterns)
|
||||||
@ -238,6 +242,9 @@ def read(stream: io.BufferedIOBase,
|
|||||||
patterns = []
|
patterns = []
|
||||||
for structure in lib:
|
for structure in lib:
|
||||||
pat = Pattern(name=structure.name.decode('ASCII'))
|
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:
|
for element in structure:
|
||||||
# Switch based on element type:
|
# Switch based on element type:
|
||||||
if isinstance(element, gdsii.elements.Boundary):
|
if isinstance(element, gdsii.elements.Boundary):
|
||||||
@ -343,6 +350,7 @@ def _ref_to_subpat(element: Union[gdsii.elements.SRef,
|
|||||||
rotation=rotation,
|
rotation=rotation,
|
||||||
scale=scale,
|
scale=scale,
|
||||||
mirrored=(mirror_across_x, False),
|
mirrored=(mirror_across_x, False),
|
||||||
|
annotations=_properties_to_annotations(element.properties),
|
||||||
repetition=repetition)
|
repetition=repetition)
|
||||||
subpat.identifier = (element.struct_name,)
|
subpat.identifier = (element.struct_name,)
|
||||||
return subpat
|
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,
|
'width': element.width if element.width is not None else 0.0,
|
||||||
'cap': cap,
|
'cap': cap,
|
||||||
'offset': numpy.zeros(2),
|
'offset': numpy.zeros(2),
|
||||||
|
'annotations':_properties_to_annotations(element.properties),
|
||||||
'raw': raw_mode,
|
'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),
|
args = {'vertices': element.xy[:-1].astype(float),
|
||||||
'layer': (element.layer, element.data_type),
|
'layer': (element.layer, element.data_type),
|
||||||
'offset': numpy.zeros(2),
|
'offset': numpy.zeros(2),
|
||||||
|
'annotations':_properties_to_annotations(element.properties),
|
||||||
'raw': raw_mode,
|
'raw': raw_mode,
|
||||||
}
|
}
|
||||||
return Polygon(**args)
|
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
|
# strans must be non-None for angle and mag to take effect
|
||||||
ref.strans = set_bit(0, 15 - 0, mirror_across_x)
|
ref.strans = set_bit(0, 15 - 0, mirror_across_x)
|
||||||
ref.mag = subpat.scale
|
ref.mag = subpat.scale
|
||||||
|
ref.properties = _annotations_to_properties(subpat.annotations, 512)
|
||||||
|
|
||||||
refs += new_refs
|
refs += new_refs
|
||||||
return 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],
|
def _shapes_to_elements(shapes: List[Shape],
|
||||||
polygonize_paths: bool = False
|
polygonize_paths: bool = False
|
||||||
) -> List[Union[gdsii.elements.Boundary, gdsii.elements.Path]]:
|
) -> 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
|
# Add a Boundary element for each shape, and Path elements if necessary
|
||||||
for shape in shapes:
|
for shape in shapes:
|
||||||
layer, data_type = _mlayer2gds(shape.layer)
|
layer, data_type = _mlayer2gds(shape.layer)
|
||||||
|
properties = _annotations_to_properties(shape.annotations, 128)
|
||||||
if isinstance(shape, Path) and not polygonize_paths:
|
if isinstance(shape, Path) and not polygonize_paths:
|
||||||
xy = numpy.round(shape.vertices + shape.offset).astype(int)
|
xy = numpy.round(shape.vertices + shape.offset).astype(int)
|
||||||
width = numpy.round(shape.width).astype(int)
|
width = numpy.round(shape.width).astype(int)
|
||||||
@ -441,26 +479,32 @@ def _shapes_to_elements(shapes: List[Shape],
|
|||||||
xy=xy)
|
xy=xy)
|
||||||
path.path_type = path_type
|
path.path_type = path_type
|
||||||
path.width = width
|
path.width = width
|
||||||
|
path.properties = properties
|
||||||
elements.append(path)
|
elements.append(path)
|
||||||
else:
|
else:
|
||||||
for polygon in shape.to_polygons():
|
for polygon in shape.to_polygons():
|
||||||
xy_open = numpy.round(polygon.vertices + polygon.offset).astype(int)
|
xy_open = numpy.round(polygon.vertices + polygon.offset).astype(int)
|
||||||
xy_closed = numpy.vstack((xy_open, xy_open[0, :]))
|
xy_closed = numpy.vstack((xy_open, xy_open[0, :]))
|
||||||
elements.append(gdsii.elements.Boundary(layer=layer,
|
boundary = gdsii.elements.Boundary(layer=layer,
|
||||||
data_type=data_type,
|
data_type=data_type,
|
||||||
xy=xy_closed))
|
xy=xy_closed)
|
||||||
|
boundary.properties = properties
|
||||||
|
elements.append(boundary)
|
||||||
return elements
|
return elements
|
||||||
|
|
||||||
|
|
||||||
def _labels_to_texts(labels: List[Label]) -> List[gdsii.elements.Text]:
|
def _labels_to_texts(labels: List[Label]) -> List[gdsii.elements.Text]:
|
||||||
texts = []
|
texts = []
|
||||||
for label in labels:
|
for label in labels:
|
||||||
|
properties = _annotations_to_properties(label.annotations, 128)
|
||||||
layer, text_type = _mlayer2gds(label.layer)
|
layer, text_type = _mlayer2gds(label.layer)
|
||||||
xy = numpy.round([label.offset]).astype(int)
|
xy = numpy.round([label.offset]).astype(int)
|
||||||
texts.append(gdsii.elements.Text(layer=layer,
|
text = gdsii.elements.Text(layer=layer,
|
||||||
text_type=text_type,
|
text_type=text_type,
|
||||||
xy=xy,
|
xy=xy,
|
||||||
string=label.string.encode('ASCII')))
|
string=label.string.encode('ASCII'))
|
||||||
|
text.properties = properties
|
||||||
|
texts.append(text)
|
||||||
return texts
|
return texts
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,19 +20,19 @@ import struct
|
|||||||
import logging
|
import logging
|
||||||
import pathlib
|
import pathlib
|
||||||
import gzip
|
import gzip
|
||||||
import numpy
|
|
||||||
from numpy import pi
|
|
||||||
|
|
||||||
|
import numpy # type: ignore
|
||||||
|
from numpy import pi
|
||||||
import fatamorgana
|
import fatamorgana
|
||||||
import fatamorgana.records as fatrec
|
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 .utils import mangle_name, make_dose_table
|
||||||
from .. import Pattern, SubPattern, PatternError, Label, Shape
|
from .. import Pattern, SubPattern, PatternError, Label, Shape
|
||||||
from ..shapes import Polygon, Path, Circle
|
from ..shapes import Polygon, Path, Circle
|
||||||
from ..repetition import Grid, Arbitrary, Repetition
|
from ..repetition import Grid, Arbitrary, Repetition
|
||||||
from ..utils import rotation_matrix_2d, get_bit, set_bit, vector2, is_scalar, layer_t
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -52,9 +52,11 @@ path_cap_map = {
|
|||||||
|
|
||||||
def build(patterns: Union[Pattern, List[Pattern]],
|
def build(patterns: Union[Pattern, List[Pattern]],
|
||||||
units_per_micron: int,
|
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,
|
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:
|
) -> fatamorgana.OasisLayout:
|
||||||
"""
|
"""
|
||||||
Convert a `Pattern` or list of patterns to an OASIS stream, writing patterns
|
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`.
|
Default `False`.
|
||||||
disambiguate_func: Function which takes a list of patterns and alters them
|
disambiguate_func: Function which takes a list of patterns and alters them
|
||||||
to make their names valid and unique. Default is `disambiguate_pattern_names`.
|
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:
|
Returns:
|
||||||
`fatamorgana.OasisLayout`
|
`fatamorgana.OasisLayout`
|
||||||
@ -104,11 +107,15 @@ def build(patterns: Union[Pattern, List[Pattern]],
|
|||||||
if disambiguate_func is None:
|
if disambiguate_func is None:
|
||||||
disambiguate_func = disambiguate_pattern_names
|
disambiguate_func = disambiguate_pattern_names
|
||||||
|
|
||||||
|
if annotations is None:
|
||||||
|
annotations = {}
|
||||||
|
|
||||||
if not modify_originals:
|
if not modify_originals:
|
||||||
patterns = [p.deepunlock() for p in copy.deepcopy(patterns)]
|
patterns = [p.deepunlock() for p in copy.deepcopy(patterns)]
|
||||||
|
|
||||||
# Create library
|
# Create library
|
||||||
lib = fatamorgana.OasisLayout(unit=units_per_micron, validation=None)
|
lib = fatamorgana.OasisLayout(unit=units_per_micron, validation=None)
|
||||||
|
lib.properties = annotations_to_properties(annotations)
|
||||||
|
|
||||||
if layer_map:
|
if layer_map:
|
||||||
for name, layer_num in layer_map.items():
|
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)
|
structure = fatamorgana.Cell(name=pat.name)
|
||||||
lib.cells.append(structure)
|
lib.cells.append(structure)
|
||||||
|
|
||||||
|
structure.properties += annotations_to_properties(pat.annotations)
|
||||||
|
|
||||||
structure.geometry += _shapes_to_elements(pat.shapes, layer2oas)
|
structure.geometry += _shapes_to_elements(pat.shapes, layer2oas)
|
||||||
structure.geometry += _labels_to_texts(pat.labels, 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
|
return lib
|
||||||
|
|
||||||
@ -226,6 +235,8 @@ def read(stream: io.BufferedIOBase,
|
|||||||
|
|
||||||
Additional library info is returned in a dict, containing:
|
Additional library info is returned in a dict, containing:
|
||||||
'units_per_micrometer': number of database units per micrometer (all values are in database units)
|
'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:
|
Args:
|
||||||
stream: Stream to read from.
|
stream: Stream to read from.
|
||||||
@ -242,6 +253,7 @@ def read(stream: io.BufferedIOBase,
|
|||||||
|
|
||||||
library_info: Dict[str, Any] = {
|
library_info: Dict[str, Any] = {
|
||||||
'units_per_micrometer': lib.unit,
|
'units_per_micrometer': lib.unit,
|
||||||
|
'annotations': properties_to_annotations(lib.properties, lib.propnames, lib.propstrings),
|
||||||
}
|
}
|
||||||
|
|
||||||
layer_map = {}
|
layer_map = {}
|
||||||
@ -252,7 +264,7 @@ def read(stream: io.BufferedIOBase,
|
|||||||
patterns = []
|
patterns = []
|
||||||
for cell in lib.cells:
|
for cell in lib.cells:
|
||||||
if isinstance(cell.name, int):
|
if isinstance(cell.name, int):
|
||||||
cell_name = lib.cellnames[cell.name].string
|
cell_name = lib.cellnames[cell.name].nstring.string
|
||||||
else:
|
else:
|
||||||
cell_name = cell.name.string
|
cell_name = cell.name.string
|
||||||
|
|
||||||
@ -263,15 +275,16 @@ def read(stream: io.BufferedIOBase,
|
|||||||
# note XELEMENT has no repetition
|
# note XELEMENT has no repetition
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
||||||
repetition = repetition_fata2masq(element.repetition)
|
repetition = repetition_fata2masq(element.repetition)
|
||||||
|
|
||||||
# Switch based on element type:
|
# Switch based on element type:
|
||||||
if isinstance(element, fatrec.Polygon):
|
if isinstance(element, fatrec.Polygon):
|
||||||
vertices = numpy.cumsum(numpy.vstack(((0, 0), element.get_point_list())), axis=0)
|
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,
|
poly = Polygon(vertices=vertices,
|
||||||
layer=element.get_layer_tuple(),
|
layer=element.get_layer_tuple(),
|
||||||
offset=element.get_xy(),
|
offset=element.get_xy(),
|
||||||
|
annotations=annotations,
|
||||||
repetition=repetition)
|
repetition=repetition)
|
||||||
|
|
||||||
if clean_vertices:
|
if clean_vertices:
|
||||||
@ -295,10 +308,13 @@ def read(stream: io.BufferedIOBase,
|
|||||||
if cap == Path.Cap.SquareCustom:
|
if cap == Path.Cap.SquareCustom:
|
||||||
path_args['cap_extensions'] = numpy.array((element.get_extension_start()[1],
|
path_args['cap_extensions'] = numpy.array((element.get_extension_start()[1],
|
||||||
element.get_extension_end()[1]))
|
element.get_extension_end()[1]))
|
||||||
|
|
||||||
|
annotations = properties_to_annotations(element.properties, lib.propnames, lib.propstrings)
|
||||||
path = Path(vertices=vertices,
|
path = Path(vertices=vertices,
|
||||||
layer=element.get_layer_tuple(),
|
layer=element.get_layer_tuple(),
|
||||||
offset=element.get_xy(),
|
offset=element.get_xy(),
|
||||||
repetition=repetition,
|
repetition=repetition,
|
||||||
|
annotations=annotations,
|
||||||
width=element.get_half_width() * 2,
|
width=element.get_half_width() * 2,
|
||||||
cap=cap,
|
cap=cap,
|
||||||
**path_args)
|
**path_args)
|
||||||
@ -314,10 +330,12 @@ def read(stream: io.BufferedIOBase,
|
|||||||
elif isinstance(element, fatrec.Rectangle):
|
elif isinstance(element, fatrec.Rectangle):
|
||||||
width = element.get_width()
|
width = element.get_width()
|
||||||
height = element.get_height()
|
height = element.get_height()
|
||||||
|
annotations = properties_to_annotations(element.properties, lib.propnames, lib.propstrings)
|
||||||
rect = Polygon(layer=element.get_layer_tuple(),
|
rect = Polygon(layer=element.get_layer_tuple(),
|
||||||
offset=element.get_xy(),
|
offset=element.get_xy(),
|
||||||
repetition=repetition,
|
repetition=repetition,
|
||||||
vertices=numpy.array(((0, 0), (1, 0), (1, 1), (0, 1))) * (width, height),
|
vertices=numpy.array(((0, 0), (1, 0), (1, 1), (0, 1))) * (width, height),
|
||||||
|
annotations=annotations,
|
||||||
)
|
)
|
||||||
pat.shapes.append(rect)
|
pat.shapes.append(rect)
|
||||||
|
|
||||||
@ -346,10 +364,12 @@ def read(stream: io.BufferedIOBase,
|
|||||||
else:
|
else:
|
||||||
vertices[2, 0] -= b
|
vertices[2, 0] -= b
|
||||||
|
|
||||||
|
annotations = properties_to_annotations(element.properties, lib.propnames, lib.propstrings)
|
||||||
trapz = Polygon(layer=element.get_layer_tuple(),
|
trapz = Polygon(layer=element.get_layer_tuple(),
|
||||||
offset=element.get_xy(),
|
offset=element.get_xy(),
|
||||||
repetition=repetition,
|
repetition=repetition,
|
||||||
vertices=vertices,
|
vertices=vertices,
|
||||||
|
annotations=annotations,
|
||||||
)
|
)
|
||||||
pat.shapes.append(trapz)
|
pat.shapes.append(trapz)
|
||||||
|
|
||||||
@ -399,24 +419,30 @@ def read(stream: io.BufferedIOBase,
|
|||||||
vertices = vertices[[0, 2, 3], :]
|
vertices = vertices[[0, 2, 3], :]
|
||||||
vertices[0, 1] += width
|
vertices[0, 1] += width
|
||||||
|
|
||||||
|
annotations = properties_to_annotations(element.properties, lib.propnames, lib.propstrings)
|
||||||
ctrapz = Polygon(layer=element.get_layer_tuple(),
|
ctrapz = Polygon(layer=element.get_layer_tuple(),
|
||||||
offset=element.get_xy(),
|
offset=element.get_xy(),
|
||||||
repetition=repetition,
|
repetition=repetition,
|
||||||
vertices=vertices,
|
vertices=vertices,
|
||||||
|
annotations=annotations,
|
||||||
)
|
)
|
||||||
pat.shapes.append(ctrapz)
|
pat.shapes.append(ctrapz)
|
||||||
|
|
||||||
elif isinstance(element, fatrec.Circle):
|
elif isinstance(element, fatrec.Circle):
|
||||||
|
annotations = properties_to_annotations(element.properties, lib.propnames, lib.propstrings)
|
||||||
circle = Circle(layer=element.get_layer_tuple(),
|
circle = Circle(layer=element.get_layer_tuple(),
|
||||||
offset=element.get_xy(),
|
offset=element.get_xy(),
|
||||||
repetition=repetition,
|
repetition=repetition,
|
||||||
|
annotations=annotations,
|
||||||
radius=float(element.get_radius()))
|
radius=float(element.get_radius()))
|
||||||
pat.shapes.append(circle)
|
pat.shapes.append(circle)
|
||||||
|
|
||||||
elif isinstance(element, fatrec.Text):
|
elif isinstance(element, fatrec.Text):
|
||||||
|
annotations = properties_to_annotations(element.properties, lib.propnames, lib.propstrings)
|
||||||
label = Label(layer=element.get_layer_tuple(),
|
label = Label(layer=element.get_layer_tuple(),
|
||||||
offset=element.get_xy(),
|
offset=element.get_xy(),
|
||||||
repetition=repetition,
|
repetition=repetition,
|
||||||
|
annotations=annotations,
|
||||||
string=str(element.get_string()))
|
string=str(element.get_string()))
|
||||||
pat.labels.append(label)
|
pat.labels.append(label)
|
||||||
|
|
||||||
@ -425,7 +451,7 @@ def read(stream: io.BufferedIOBase,
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
for placement in cell.placements:
|
for placement in cell.placements:
|
||||||
pat.subpatterns.append(_placement_to_subpat(placement))
|
pat.subpatterns.append(_placement_to_subpat(placement, lib))
|
||||||
|
|
||||||
patterns.append(pat)
|
patterns.append(pat)
|
||||||
|
|
||||||
@ -435,7 +461,7 @@ def read(stream: io.BufferedIOBase,
|
|||||||
for p in patterns_dict.values():
|
for p in patterns_dict.values():
|
||||||
for sp in p.subpatterns:
|
for sp in p.subpatterns:
|
||||||
ident = sp.identifier[0]
|
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]
|
sp.pattern = patterns_dict[name]
|
||||||
del sp.identifier
|
del sp.identifier
|
||||||
|
|
||||||
@ -459,7 +485,7 @@ def _mlayer2oas(mlayer: layer_t) -> Tuple[int, int]:
|
|||||||
return layer, data_type
|
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
|
Helper function to create a SubPattern from a placment. Sets subpat.pattern to None
|
||||||
and sets the instance .identifier to (struct_name,).
|
and sets the instance .identifier to (struct_name,).
|
||||||
@ -468,20 +494,19 @@ def _placement_to_subpat(placement: fatrec.Placement) -> SubPattern:
|
|||||||
mag = placement.magnification if placement.magnification is not None else 1
|
mag = placement.magnification if placement.magnification is not None else 1
|
||||||
pname = placement.get_name()
|
pname = placement.get_name()
|
||||||
name = pname if isinstance(pname, int) else pname.string
|
name = pname if isinstance(pname, int) else pname.string
|
||||||
args: Dict[str, Any] = {
|
annotations = properties_to_annotations(placement.properties, lib.propnames, lib.propstrings)
|
||||||
'pattern': None,
|
subpat = SubPattern(offset=xy,
|
||||||
'mirrored': (placement.flip, False),
|
pattern=None,
|
||||||
'rotation': float(placement.angle * pi/180),
|
mirrored=(placement.flip, False),
|
||||||
'scale': mag,
|
rotation=float(placement.angle * pi/180),
|
||||||
'identifier': (name,),
|
scale=float(mag),
|
||||||
'repetition': repetition_fata2masq(placement.repetition),
|
identifier=(name,),
|
||||||
}
|
repetition=repetition_fata2masq(placement.repetition),
|
||||||
|
annotations=annotations)
|
||||||
subpat = SubPattern(offset=xy, **args)
|
|
||||||
return subpat
|
return subpat
|
||||||
|
|
||||||
|
|
||||||
def _subpatterns_to_refs(subpatterns: List[SubPattern]
|
def _subpatterns_to_placements(subpatterns: List[SubPattern]
|
||||||
) -> List[fatrec.Placement]:
|
) -> List[fatrec.Placement]:
|
||||||
refs = []
|
refs = []
|
||||||
for subpat in subpatterns:
|
for subpat in subpatterns:
|
||||||
@ -493,19 +518,16 @@ def _subpatterns_to_refs(subpatterns: List[SubPattern]
|
|||||||
frep, rep_offset = repetition_masq2fata(subpat.repetition)
|
frep, rep_offset = repetition_masq2fata(subpat.repetition)
|
||||||
|
|
||||||
offset = numpy.round(subpat.offset + rep_offset).astype(int)
|
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
|
angle = numpy.rad2deg(subpat.rotation + extra_angle) % 360
|
||||||
ref = fatrec.Placement(
|
ref = fatrec.Placement(
|
||||||
name=subpat.pattern.name,
|
name=subpat.pattern.name,
|
||||||
flip=mirror_across_x,
|
flip=mirror_across_x,
|
||||||
angle=angle,
|
angle=angle,
|
||||||
magnification=subpat.scale,
|
magnification=subpat.scale,
|
||||||
**args)
|
properties=annotations_to_properties(subpat.annotations),
|
||||||
|
x=offset[0],
|
||||||
|
y=offset[1],
|
||||||
|
repetition=frep)
|
||||||
|
|
||||||
refs.append(ref)
|
refs.append(ref)
|
||||||
return refs
|
return refs
|
||||||
@ -519,6 +541,7 @@ def _shapes_to_elements(shapes: List[Shape],
|
|||||||
for shape in shapes:
|
for shape in shapes:
|
||||||
layer, datatype = layer2oas(shape.layer)
|
layer, datatype = layer2oas(shape.layer)
|
||||||
repetition, rep_offset = repetition_masq2fata(shape.repetition)
|
repetition, rep_offset = repetition_masq2fata(shape.repetition)
|
||||||
|
properties = annotations_to_properties(shape.annotations)
|
||||||
if isinstance(shape, Circle):
|
if isinstance(shape, Circle):
|
||||||
offset = numpy.round(shape.offset + rep_offset).astype(int)
|
offset = numpy.round(shape.offset + rep_offset).astype(int)
|
||||||
radius = numpy.round(shape.radius).astype(int)
|
radius = numpy.round(shape.radius).astype(int)
|
||||||
@ -527,6 +550,7 @@ def _shapes_to_elements(shapes: List[Shape],
|
|||||||
radius=radius,
|
radius=radius,
|
||||||
x=offset[0],
|
x=offset[0],
|
||||||
y=offset[1],
|
y=offset[1],
|
||||||
|
properties=properties,
|
||||||
repetition=repetition)
|
repetition=repetition)
|
||||||
elements.append(circle)
|
elements.append(circle)
|
||||||
elif isinstance(shape, Path):
|
elif isinstance(shape, Path):
|
||||||
@ -544,6 +568,7 @@ def _shapes_to_elements(shapes: List[Shape],
|
|||||||
y=xy[1],
|
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,
|
extension_end=extension_end,
|
||||||
|
properties=properties,
|
||||||
repetition=repetition,
|
repetition=repetition,
|
||||||
)
|
)
|
||||||
elements.append(path)
|
elements.append(path)
|
||||||
@ -556,6 +581,7 @@ def _shapes_to_elements(shapes: List[Shape],
|
|||||||
x=xy[0],
|
x=xy[0],
|
||||||
y=xy[1],
|
y=xy[1],
|
||||||
point_list=points,
|
point_list=points,
|
||||||
|
properties=properties,
|
||||||
repetition=repetition))
|
repetition=repetition))
|
||||||
return elements
|
return elements
|
||||||
|
|
||||||
@ -568,11 +594,13 @@ def _labels_to_texts(labels: List[Label],
|
|||||||
layer, datatype = layer2oas(label.layer)
|
layer, datatype = layer2oas(label.layer)
|
||||||
repetition, rep_offset = repetition_masq2fata(label.repetition)
|
repetition, rep_offset = repetition_masq2fata(label.repetition)
|
||||||
xy = numpy.round(label.offset + rep_offset).astype(int)
|
xy = numpy.round(label.offset + rep_offset).astype(int)
|
||||||
|
properties = annotations_to_properties(label.annotations)
|
||||||
texts.append(fatrec.Text(layer=layer,
|
texts.append(fatrec.Text(layer=layer,
|
||||||
datatype=datatype,
|
datatype=datatype,
|
||||||
x=xy[0],
|
x=xy[0],
|
||||||
y=xy[1],
|
y=xy[1],
|
||||||
string=label.string,
|
string=label.string,
|
||||||
|
properties=properties,
|
||||||
repetition=repetition))
|
repetition=repetition))
|
||||||
return texts
|
return texts
|
||||||
|
|
||||||
@ -609,6 +637,7 @@ def disambiguate_pattern_names(patterns,
|
|||||||
|
|
||||||
def repetition_fata2masq(rep: Union[fatamorgana.GridRepetition, fatamorgana.ArbitraryRepetition, None]
|
def repetition_fata2masq(rep: Union[fatamorgana.GridRepetition, fatamorgana.ArbitraryRepetition, None]
|
||||||
) -> Optional[Repetition]:
|
) -> Optional[Repetition]:
|
||||||
|
mrep: Optional[Repetition]
|
||||||
if isinstance(rep, fatamorgana.GridRepetition):
|
if isinstance(rep, fatamorgana.GridRepetition):
|
||||||
mrep = Grid(a_vector=rep.a_vector,
|
mrep = Grid(a_vector=rep.a_vector,
|
||||||
b_vector=rep.b_vector,
|
b_vector=rep.b_vector,
|
||||||
@ -624,7 +653,12 @@ def repetition_fata2masq(rep: Union[fatamorgana.GridRepetition, fatamorgana.Arbi
|
|||||||
return mrep
|
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):
|
if isinstance(rep, Grid):
|
||||||
frep = fatamorgana.GridRepetition(
|
frep = fatamorgana.GridRepetition(
|
||||||
a_vector=numpy.round(rep.a_vector).astype(int),
|
a_vector=numpy.round(rep.a_vector).astype(int),
|
||||||
@ -642,3 +676,46 @@ def repetition_masq2fata(rep: Optional[Repetition]):
|
|||||||
frep = None
|
frep = None
|
||||||
offset = (0, 0)
|
offset = (0, 0)
|
||||||
return frep, offset
|
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
|
SVG file format readers and writers
|
||||||
"""
|
"""
|
||||||
from typing import Dict, Optional
|
from typing import Dict, Optional
|
||||||
import svgwrite
|
|
||||||
import numpy
|
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
import numpy # type: ignore
|
||||||
|
import svgwrite # type: ignore
|
||||||
|
|
||||||
from .utils import mangle_name
|
from .utils import mangle_name
|
||||||
from .. import Pattern
|
from .. import Pattern
|
||||||
|
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
from typing import List, Tuple, Dict, Optional
|
from typing import List, Tuple, Dict, Optional
|
||||||
import copy
|
import copy
|
||||||
import numpy
|
import numpy # type: ignore
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
||||||
from .repetition import Repetition
|
from .repetition import Repetition
|
||||||
from .error import PatternError, PatternLockedError
|
from .error import PatternError, PatternLockedError
|
||||||
from .utils import is_scalar, vector2, rotation_matrix_2d, layer_t, AutoSlots
|
from .utils import is_scalar, vector2, rotation_matrix_2d, layer_t, AutoSlots, annotations_t
|
||||||
from .traits import PositionableImpl, LayerableImpl, Copyable, Pivotable, LockableImpl, RepeatableImpl
|
from .traits import PositionableImpl, LayerableImpl, Copyable, Pivotable, LockableImpl, RepeatableImpl
|
||||||
|
from .traits import AnnotatableImpl
|
||||||
|
|
||||||
|
|
||||||
class Label(PositionableImpl, LayerableImpl, LockableImpl, RepeatableImpl,
|
class Label(PositionableImpl, LayerableImpl, LockableImpl, RepeatableImpl, AnnotatableImpl,
|
||||||
Pivotable, Copyable, metaclass=AutoSlots):
|
Pivotable, Copyable, metaclass=AutoSlots):
|
||||||
"""
|
"""
|
||||||
A text annotation with a position and layer (but no size; it is not drawn)
|
A text annotation with a position and layer (but no size; it is not drawn)
|
||||||
@ -42,14 +43,17 @@ class Label(PositionableImpl, LayerableImpl, LockableImpl, RepeatableImpl,
|
|||||||
offset: vector2 = (0.0, 0.0),
|
offset: vector2 = (0.0, 0.0),
|
||||||
layer: layer_t = 0,
|
layer: layer_t = 0,
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
locked: bool = False):
|
annotations: Optional[annotations_t] = None,
|
||||||
object.__setattr__(self, 'locked', False)
|
locked: bool = False,
|
||||||
|
):
|
||||||
|
LockableImpl.unlock(self)
|
||||||
self.identifier = ()
|
self.identifier = ()
|
||||||
self.string = string
|
self.string = string
|
||||||
self.offset = numpy.array(offset, dtype=float, copy=True)
|
self.offset = numpy.array(offset, dtype=float, copy=True)
|
||||||
self.layer = layer
|
self.layer = layer
|
||||||
self.repetition = repetition
|
self.repetition = repetition
|
||||||
self.locked = locked
|
self.annotations = annotations if annotations is not None else {}
|
||||||
|
self.set_locked(locked)
|
||||||
|
|
||||||
def __copy__(self) -> 'Label':
|
def __copy__(self) -> 'Label':
|
||||||
return Label(string=self.string,
|
return Label(string=self.string,
|
||||||
@ -62,7 +66,7 @@ class Label(PositionableImpl, LayerableImpl, LockableImpl, RepeatableImpl,
|
|||||||
memo = {} if memo is None else memo
|
memo = {} if memo is None else memo
|
||||||
new = copy.copy(self).unlock()
|
new = copy.copy(self).unlock()
|
||||||
new._offset = self._offset.copy()
|
new._offset = self._offset.copy()
|
||||||
new.locked = self.locked
|
new.set_locked(self.locked)
|
||||||
return new
|
return new
|
||||||
|
|
||||||
def rotate_around(self, pivot: vector2, rotation: float) -> 'Label':
|
def rotate_around(self, pivot: vector2, rotation: float) -> 'Label':
|
||||||
|
@ -9,26 +9,27 @@ import itertools
|
|||||||
import pickle
|
import pickle
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
import numpy
|
import numpy # type: ignore
|
||||||
from numpy import inf
|
from numpy import inf
|
||||||
# .visualize imports matplotlib and matplotlib.collections
|
# .visualize imports matplotlib and matplotlib.collections
|
||||||
|
|
||||||
from .subpattern import SubPattern
|
from .subpattern import SubPattern
|
||||||
from .shapes import Shape, Polygon
|
from .shapes import Shape, Polygon
|
||||||
from .label import Label
|
from .label import Label
|
||||||
from .utils import rotation_matrix_2d, vector2, normalize_mirror
|
from .utils import rotation_matrix_2d, vector2, normalize_mirror, AutoSlots, annotations_t
|
||||||
from .error import PatternError, PatternLockedError
|
from .error import PatternError, PatternLockedError
|
||||||
|
from .traits import LockableImpl, AnnotatableImpl
|
||||||
|
|
||||||
|
|
||||||
visitor_function_t = Callable[['Pattern', Tuple['Pattern'], Dict, numpy.ndarray], 'Pattern']
|
visitor_function_t = Callable[['Pattern', Tuple['Pattern'], Dict, numpy.ndarray], 'Pattern']
|
||||||
|
|
||||||
|
|
||||||
class Pattern:
|
class Pattern(LockableImpl, AnnotatableImpl, metaclass=AutoSlots):
|
||||||
"""
|
"""
|
||||||
2D layout consisting of some set of shapes, labels, and references to other Pattern objects
|
2D layout consisting of some set of shapes, labels, and references to other Pattern objects
|
||||||
(via SubPattern). Shapes are assumed to inherit from masque.shapes.Shape or provide equivalent functions.
|
(via SubPattern). Shapes are assumed to inherit from masque.shapes.Shape or provide equivalent functions.
|
||||||
"""
|
"""
|
||||||
__slots__ = ('shapes', 'labels', 'subpatterns', 'name', 'locked')
|
__slots__ = ('shapes', 'labels', 'subpatterns', 'name')
|
||||||
|
|
||||||
shapes: List[Shape]
|
shapes: List[Shape]
|
||||||
""" List of all shapes in this Pattern.
|
""" List of all shapes in this Pattern.
|
||||||
@ -47,14 +48,12 @@ class Pattern:
|
|||||||
name: str
|
name: str
|
||||||
""" A name for this pattern """
|
""" A name for this pattern """
|
||||||
|
|
||||||
locked: bool
|
|
||||||
""" When the pattern is locked, no changes may be made. """
|
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
name: str = '',
|
name: str = '',
|
||||||
shapes: Sequence[Shape] = (),
|
shapes: Sequence[Shape] = (),
|
||||||
labels: Sequence[Label] = (),
|
labels: Sequence[Label] = (),
|
||||||
subpatterns: Sequence[SubPattern] = (),
|
subpatterns: Sequence[SubPattern] = (),
|
||||||
|
annotations: Optional[annotations_t] = None,
|
||||||
locked: bool = False,
|
locked: bool = False,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@ -68,7 +67,7 @@ class Pattern:
|
|||||||
name: An identifier for the Pattern
|
name: An identifier for the Pattern
|
||||||
locked: Whether to lock the pattern after construction
|
locked: Whether to lock the pattern after construction
|
||||||
"""
|
"""
|
||||||
object.__setattr__(self, 'locked', False)
|
LockableImpl.unlock(self)
|
||||||
if isinstance(shapes, list):
|
if isinstance(shapes, list):
|
||||||
self.shapes = shapes
|
self.shapes = shapes
|
||||||
else:
|
else:
|
||||||
@ -84,8 +83,9 @@ class Pattern:
|
|||||||
else:
|
else:
|
||||||
self.subpatterns = list(subpatterns)
|
self.subpatterns = list(subpatterns)
|
||||||
|
|
||||||
|
self.annotations = annotations if annotations is not None else {}
|
||||||
self.name = name
|
self.name = name
|
||||||
self.locked = locked
|
self.set_locked(locked)
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
def __setattr__(self, name, value):
|
||||||
if self.locked and name != 'locked':
|
if self.locked and name != 'locked':
|
||||||
@ -97,6 +97,7 @@ class Pattern:
|
|||||||
shapes=copy.deepcopy(self.shapes),
|
shapes=copy.deepcopy(self.shapes),
|
||||||
labels=copy.deepcopy(self.labels),
|
labels=copy.deepcopy(self.labels),
|
||||||
subpatterns=[copy.copy(sp) for sp in self.subpatterns],
|
subpatterns=[copy.copy(sp) for sp in self.subpatterns],
|
||||||
|
annotations=copy.deepcopy(self.annotations),
|
||||||
locked=self.locked)
|
locked=self.locked)
|
||||||
|
|
||||||
def __deepcopy__(self, memo: Dict = None) -> 'Pattern':
|
def __deepcopy__(self, memo: Dict = None) -> 'Pattern':
|
||||||
@ -105,6 +106,7 @@ class Pattern:
|
|||||||
shapes=copy.deepcopy(self.shapes, memo),
|
shapes=copy.deepcopy(self.shapes, memo),
|
||||||
labels=copy.deepcopy(self.labels, memo),
|
labels=copy.deepcopy(self.labels, memo),
|
||||||
subpatterns=copy.deepcopy(self.subpatterns, memo),
|
subpatterns=copy.deepcopy(self.subpatterns, memo),
|
||||||
|
annotations=copy.deepcopy(self.annotations, memo),
|
||||||
locked=self.locked)
|
locked=self.locked)
|
||||||
return new
|
return new
|
||||||
|
|
||||||
@ -815,7 +817,7 @@ class Pattern:
|
|||||||
self.shapes = tuple(self.shapes)
|
self.shapes = tuple(self.shapes)
|
||||||
self.labels = tuple(self.labels)
|
self.labels = tuple(self.labels)
|
||||||
self.subpatterns = tuple(self.subpatterns)
|
self.subpatterns = tuple(self.subpatterns)
|
||||||
object.__setattr__(self, 'locked', True)
|
LockableImpl.lock(self)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def unlock(self) -> 'Pattern':
|
def unlock(self) -> 'Pattern':
|
||||||
@ -826,7 +828,7 @@ class Pattern:
|
|||||||
self
|
self
|
||||||
"""
|
"""
|
||||||
if self.locked:
|
if self.locked:
|
||||||
object.__setattr__(self, 'locked', False)
|
LockableImpl.unlock(self)
|
||||||
self.shapes = list(self.shapes)
|
self.shapes = list(self.shapes)
|
||||||
self.labels = list(self.labels)
|
self.labels = list(self.labels)
|
||||||
self.subpatterns = list(self.subpatterns)
|
self.subpatterns = list(self.subpatterns)
|
||||||
|
@ -7,7 +7,7 @@ from typing import Union, List, Dict, Tuple, Optional, Sequence, TYPE_CHECKING,
|
|||||||
import copy
|
import copy
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
|
|
||||||
import numpy
|
import numpy # type: ignore
|
||||||
|
|
||||||
from .error import PatternError, PatternLockedError
|
from .error import PatternError, PatternLockedError
|
||||||
from .utils import rotation_matrix_2d, vector2, AutoSlots
|
from .utils import rotation_matrix_2d, vector2, AutoSlots
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
from typing import List, Tuple, Dict, Optional, Sequence
|
from typing import List, Tuple, Dict, Optional, Sequence
|
||||||
import copy
|
import copy
|
||||||
import math
|
import math
|
||||||
import numpy
|
|
||||||
|
import numpy # type: ignore
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
||||||
from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS
|
from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS
|
||||||
from .. import PatternError
|
from .. import PatternError
|
||||||
from ..repetition import Repetition
|
from ..repetition import Repetition
|
||||||
from ..utils import is_scalar, vector2, layer_t, AutoSlots
|
from ..utils import is_scalar, vector2, layer_t, AutoSlots, annotations_t
|
||||||
|
from ..traits import LockableImpl
|
||||||
|
|
||||||
|
|
||||||
class Arc(Shape, metaclass=AutoSlots):
|
class Arc(Shape, metaclass=AutoSlots):
|
||||||
@ -160,10 +162,11 @@ class Arc(Shape, metaclass=AutoSlots):
|
|||||||
layer: layer_t = 0,
|
layer: layer_t = 0,
|
||||||
dose: float = 1.0,
|
dose: float = 1.0,
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
|
annotations: Optional[annotations_t] = None,
|
||||||
locked: bool = False,
|
locked: bool = False,
|
||||||
raw: bool = False,
|
raw: bool = False,
|
||||||
):
|
):
|
||||||
object.__setattr__(self, 'locked', False)
|
LockableImpl.unlock(self)
|
||||||
self.identifier = ()
|
self.identifier = ()
|
||||||
if raw:
|
if raw:
|
||||||
self._radii = radii
|
self._radii = radii
|
||||||
@ -172,6 +175,7 @@ class Arc(Shape, metaclass=AutoSlots):
|
|||||||
self._offset = offset
|
self._offset = offset
|
||||||
self._rotation = rotation
|
self._rotation = rotation
|
||||||
self._repetition = repetition
|
self._repetition = repetition
|
||||||
|
self._annotations = annotations if annotations is not None else {}
|
||||||
self._layer = layer
|
self._layer = layer
|
||||||
self._dose = dose
|
self._dose = dose
|
||||||
else:
|
else:
|
||||||
@ -181,12 +185,13 @@ class Arc(Shape, metaclass=AutoSlots):
|
|||||||
self.offset = offset
|
self.offset = offset
|
||||||
self.rotation = rotation
|
self.rotation = rotation
|
||||||
self.repetition = repetition
|
self.repetition = repetition
|
||||||
|
self.annotations = annotations if annotations is not None else {}
|
||||||
self.layer = layer
|
self.layer = layer
|
||||||
self.dose = dose
|
self.dose = dose
|
||||||
self.poly_num_points = poly_num_points
|
self.poly_num_points = poly_num_points
|
||||||
self.poly_max_arclen = poly_max_arclen
|
self.poly_max_arclen = poly_max_arclen
|
||||||
[self.mirror(a) for a, do in enumerate(mirrored) if do]
|
[self.mirror(a) for a, do in enumerate(mirrored) if do]
|
||||||
self.locked = locked
|
self.set_locked(locked)
|
||||||
|
|
||||||
def __deepcopy__(self, memo: Dict = None) -> 'Arc':
|
def __deepcopy__(self, memo: Dict = None) -> 'Arc':
|
||||||
memo = {} if memo is None else memo
|
memo = {} if memo is None else memo
|
||||||
@ -194,7 +199,8 @@ class Arc(Shape, metaclass=AutoSlots):
|
|||||||
new._offset = self._offset.copy()
|
new._offset = self._offset.copy()
|
||||||
new._radii = self._radii.copy()
|
new._radii = self._radii.copy()
|
||||||
new._angles = self._angles.copy()
|
new._angles = self._angles.copy()
|
||||||
new.locked = self.locked
|
new._annotations = copy.deepcopy(self._annotations)
|
||||||
|
new.set_locked(self.locked)
|
||||||
return new
|
return new
|
||||||
|
|
||||||
def to_polygons(self,
|
def to_polygons(self,
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
from typing import List, Dict, Optional
|
from typing import List, Dict, Optional
|
||||||
import copy
|
import copy
|
||||||
import numpy
|
|
||||||
|
import numpy # type: ignore
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
||||||
from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS
|
from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS
|
||||||
from .. import PatternError
|
from .. import PatternError
|
||||||
from ..repetition import Repetition
|
from ..repetition import Repetition
|
||||||
from ..utils import is_scalar, vector2, layer_t, AutoSlots
|
from ..utils import is_scalar, vector2, layer_t, AutoSlots, annotations_t
|
||||||
|
from ..traits import LockableImpl
|
||||||
|
|
||||||
|
|
||||||
class Circle(Shape, metaclass=AutoSlots):
|
class Circle(Shape, metaclass=AutoSlots):
|
||||||
@ -48,23 +50,36 @@ class Circle(Shape, metaclass=AutoSlots):
|
|||||||
layer: layer_t = 0,
|
layer: layer_t = 0,
|
||||||
dose: float = 1.0,
|
dose: float = 1.0,
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
locked: bool = False):
|
annotations: Optional[annotations_t] = None,
|
||||||
object.__setattr__(self, 'locked', False)
|
locked: bool = False,
|
||||||
|
raw: bool = False,
|
||||||
|
):
|
||||||
|
LockableImpl.unlock(self)
|
||||||
self.identifier = ()
|
self.identifier = ()
|
||||||
self.offset = numpy.array(offset, dtype=float)
|
if raw:
|
||||||
|
self._radius = radius
|
||||||
|
self._offset = offset
|
||||||
|
self._repetition = repetition
|
||||||
|
self._annotations = annotations if annotations is not None else {}
|
||||||
|
self._layer = layer
|
||||||
|
self._dose = dose
|
||||||
|
else:
|
||||||
|
self.radius = radius
|
||||||
|
self.offset = offset
|
||||||
|
self.repetition = repetition
|
||||||
|
self.annotations = annotations if annotations is not None else {}
|
||||||
self.layer = layer
|
self.layer = layer
|
||||||
self.dose = dose
|
self.dose = dose
|
||||||
self.radius = radius
|
|
||||||
self.poly_num_points = poly_num_points
|
self.poly_num_points = poly_num_points
|
||||||
self.poly_max_arclen = poly_max_arclen
|
self.poly_max_arclen = poly_max_arclen
|
||||||
self.repetition = repetition
|
self.set_locked(locked)
|
||||||
self.locked = locked
|
|
||||||
|
|
||||||
def __deepcopy__(self, memo: Dict = None) -> 'Circle':
|
def __deepcopy__(self, memo: Dict = None) -> 'Circle':
|
||||||
memo = {} if memo is None else memo
|
memo = {} if memo is None else memo
|
||||||
new = copy.copy(self).unlock()
|
new = copy.copy(self).unlock()
|
||||||
new._offset = self._offset.copy()
|
new._offset = self._offset.copy()
|
||||||
new.locked = self.locked
|
new._annotations = copy.deepcopy(self._annotations)
|
||||||
|
new.set_locked(self.locked)
|
||||||
return new
|
return new
|
||||||
|
|
||||||
def to_polygons(self,
|
def to_polygons(self,
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
from typing import List, Tuple, Dict, Sequence, Optional
|
from typing import List, Tuple, Dict, Sequence, Optional
|
||||||
import copy
|
import copy
|
||||||
import math
|
import math
|
||||||
import numpy
|
|
||||||
|
import numpy # type: ignore
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
||||||
from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS
|
from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS
|
||||||
from .. import PatternError
|
from .. import PatternError
|
||||||
from ..repetition import Repetition
|
from ..repetition import Repetition
|
||||||
from ..utils import is_scalar, rotation_matrix_2d, vector2, layer_t, AutoSlots
|
from ..utils import is_scalar, rotation_matrix_2d, vector2, layer_t, AutoSlots, annotations_t
|
||||||
|
from ..traits import LockableImpl
|
||||||
|
|
||||||
|
|
||||||
class Ellipse(Shape, metaclass=AutoSlots):
|
class Ellipse(Shape, metaclass=AutoSlots):
|
||||||
@ -95,16 +97,18 @@ class Ellipse(Shape, metaclass=AutoSlots):
|
|||||||
layer: layer_t = 0,
|
layer: layer_t = 0,
|
||||||
dose: float = 1.0,
|
dose: float = 1.0,
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
|
annotations: Optional[annotations_t] = None,
|
||||||
locked: bool = False,
|
locked: bool = False,
|
||||||
raw: bool = False,
|
raw: bool = False,
|
||||||
):
|
):
|
||||||
object.__setattr__(self, 'locked', False)
|
LockableImpl.unlock(self)
|
||||||
self.identifier = ()
|
self.identifier = ()
|
||||||
if raw:
|
if raw:
|
||||||
self._radii = radii
|
self._radii = radii
|
||||||
self._offset = offset
|
self._offset = offset
|
||||||
self._rotation = rotation
|
self._rotation = rotation
|
||||||
self._repetition = repetition
|
self._repetition = repetition
|
||||||
|
self._annotations = annotations if annotations is not None else {}
|
||||||
self._layer = layer
|
self._layer = layer
|
||||||
self._dose = dose
|
self._dose = dose
|
||||||
else:
|
else:
|
||||||
@ -112,19 +116,21 @@ class Ellipse(Shape, metaclass=AutoSlots):
|
|||||||
self.offset = offset
|
self.offset = offset
|
||||||
self.rotation = rotation
|
self.rotation = rotation
|
||||||
self.repetition = repetition
|
self.repetition = repetition
|
||||||
|
self.annotations = annotations if annotations is not None else {}
|
||||||
self.layer = layer
|
self.layer = layer
|
||||||
self.dose = dose
|
self.dose = dose
|
||||||
[self.mirror(a) for a, do in enumerate(mirrored) if do]
|
[self.mirror(a) for a, do in enumerate(mirrored) if do]
|
||||||
self.poly_num_points = poly_num_points
|
self.poly_num_points = poly_num_points
|
||||||
self.poly_max_arclen = poly_max_arclen
|
self.poly_max_arclen = poly_max_arclen
|
||||||
self.locked = locked
|
self.set_locked(locked)
|
||||||
|
|
||||||
def __deepcopy__(self, memo: Dict = None) -> 'Ellipse':
|
def __deepcopy__(self, memo: Dict = None) -> 'Ellipse':
|
||||||
memo = {} if memo is None else memo
|
memo = {} if memo is None else memo
|
||||||
new = copy.copy(self).unlock()
|
new = copy.copy(self).unlock()
|
||||||
new._offset = self._offset.copy()
|
new._offset = self._offset.copy()
|
||||||
new._radii = self._radii.copy()
|
new._radii = self._radii.copy()
|
||||||
new.locked = self.locked
|
new._annotations = copy.deepcopy(self._annotations)
|
||||||
|
new.set_locked(self.locked)
|
||||||
return new
|
return new
|
||||||
|
|
||||||
def to_polygons(self,
|
def to_polygons(self,
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
from typing import List, Tuple, Dict, Optional, Sequence
|
from typing import List, Tuple, Dict, Optional, Sequence
|
||||||
import copy
|
import copy
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
import numpy
|
|
||||||
|
import numpy # type: ignore
|
||||||
from numpy import pi, inf
|
from numpy import pi, inf
|
||||||
|
|
||||||
from . import Shape, normalized_shape_tuple, Polygon, Circle
|
from . import Shape, normalized_shape_tuple, Polygon, Circle
|
||||||
from .. import PatternError
|
from .. import PatternError
|
||||||
from ..repetition import Repetition
|
from ..repetition import Repetition
|
||||||
from ..utils import is_scalar, rotation_matrix_2d, vector2, layer_t, AutoSlots
|
from ..utils import is_scalar, rotation_matrix_2d, vector2, layer_t, AutoSlots
|
||||||
from ..utils import remove_colinear_vertices, remove_duplicate_vertices
|
from ..utils import remove_colinear_vertices, remove_duplicate_vertices, annotations_t
|
||||||
|
from ..traits import LockableImpl
|
||||||
|
|
||||||
|
|
||||||
class PathCap(Enum):
|
class PathCap(Enum):
|
||||||
@ -149,10 +151,11 @@ class Path(Shape, metaclass=AutoSlots):
|
|||||||
layer: layer_t = 0,
|
layer: layer_t = 0,
|
||||||
dose: float = 1.0,
|
dose: float = 1.0,
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
|
annotations: Optional[annotations_t] = None,
|
||||||
locked: bool = False,
|
locked: bool = False,
|
||||||
raw: bool = False,
|
raw: bool = False,
|
||||||
):
|
):
|
||||||
object.__setattr__(self, 'locked', False)
|
LockableImpl.unlock(self)
|
||||||
self._cap_extensions = None # Since .cap setter might access it
|
self._cap_extensions = None # Since .cap setter might access it
|
||||||
|
|
||||||
self.identifier = ()
|
self.identifier = ()
|
||||||
@ -160,6 +163,7 @@ class Path(Shape, metaclass=AutoSlots):
|
|||||||
self._vertices = vertices
|
self._vertices = vertices
|
||||||
self._offset = offset
|
self._offset = offset
|
||||||
self._repetition = repetition
|
self._repetition = repetition
|
||||||
|
self._annotations = annotations if annotations is not None else {}
|
||||||
self._layer = layer
|
self._layer = layer
|
||||||
self._dose = dose
|
self._dose = dose
|
||||||
self._width = width
|
self._width = width
|
||||||
@ -169,6 +173,7 @@ class Path(Shape, metaclass=AutoSlots):
|
|||||||
self.vertices = vertices
|
self.vertices = vertices
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
self.repetition = repetition
|
self.repetition = repetition
|
||||||
|
self.annotations = annotations if annotations is not None else {}
|
||||||
self.layer = layer
|
self.layer = layer
|
||||||
self.dose = dose
|
self.dose = dose
|
||||||
self.width = width
|
self.width = width
|
||||||
@ -176,7 +181,7 @@ class Path(Shape, metaclass=AutoSlots):
|
|||||||
self.cap_extensions = cap_extensions
|
self.cap_extensions = cap_extensions
|
||||||
self.rotate(rotation)
|
self.rotate(rotation)
|
||||||
[self.mirror(a) for a, do in enumerate(mirrored) if do]
|
[self.mirror(a) for a, do in enumerate(mirrored) if do]
|
||||||
self.locked = locked
|
self.set_locked(locked)
|
||||||
|
|
||||||
def __deepcopy__(self, memo: Dict = None) -> 'Path':
|
def __deepcopy__(self, memo: Dict = None) -> 'Path':
|
||||||
memo = {} if memo is None else memo
|
memo = {} if memo is None else memo
|
||||||
@ -185,7 +190,8 @@ class Path(Shape, metaclass=AutoSlots):
|
|||||||
new._vertices = self._vertices.copy()
|
new._vertices = self._vertices.copy()
|
||||||
new._cap = copy.deepcopy(self._cap, memo)
|
new._cap = copy.deepcopy(self._cap, memo)
|
||||||
new._cap_extensions = copy.deepcopy(self._cap_extensions, memo)
|
new._cap_extensions = copy.deepcopy(self._cap_extensions, memo)
|
||||||
new.locked = self.locked
|
new._annotations = copy.deepcopy(self._annotations)
|
||||||
|
new.set_locked(self.locked)
|
||||||
return new
|
return new
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
from typing import List, Tuple, Dict, Optional, Sequence
|
from typing import List, Tuple, Dict, Optional, Sequence
|
||||||
import copy
|
import copy
|
||||||
import numpy
|
|
||||||
|
import numpy # type: ignore
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
||||||
from . import Shape, normalized_shape_tuple
|
from . import Shape, normalized_shape_tuple
|
||||||
from .. import PatternError
|
from .. import PatternError
|
||||||
from ..repetition import Repetition
|
from ..repetition import Repetition
|
||||||
from ..utils import is_scalar, rotation_matrix_2d, vector2, layer_t, AutoSlots
|
from ..utils import is_scalar, rotation_matrix_2d, vector2, layer_t, AutoSlots
|
||||||
from ..utils import remove_colinear_vertices, remove_duplicate_vertices
|
from ..utils import remove_colinear_vertices, remove_duplicate_vertices, annotations_t
|
||||||
|
from ..traits import LockableImpl
|
||||||
|
|
||||||
|
|
||||||
class Polygon(Shape, metaclass=AutoSlots):
|
class Polygon(Shape, metaclass=AutoSlots):
|
||||||
@ -77,33 +79,37 @@ class Polygon(Shape, metaclass=AutoSlots):
|
|||||||
layer: layer_t = 0,
|
layer: layer_t = 0,
|
||||||
dose: float = 1.0,
|
dose: float = 1.0,
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
|
annotations: Optional[annotations_t] = None,
|
||||||
locked: bool = False,
|
locked: bool = False,
|
||||||
raw: bool = False,
|
raw: bool = False,
|
||||||
):
|
):
|
||||||
object.__setattr__(self, 'locked', False)
|
LockableImpl.unlock(self)
|
||||||
self.identifier = ()
|
self.identifier = ()
|
||||||
if raw:
|
if raw:
|
||||||
self._vertices = vertices
|
self._vertices = vertices
|
||||||
self._offset = offset
|
self._offset = offset
|
||||||
self._repetition = repetition
|
self._repetition = repetition
|
||||||
|
self._annotations = annotations if annotations is not None else {}
|
||||||
self._layer = layer
|
self._layer = layer
|
||||||
self._dose = dose
|
self._dose = dose
|
||||||
else:
|
else:
|
||||||
self.vertices = vertices
|
self.vertices = vertices
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
self.repetition = repetition
|
self.repetition = repetition
|
||||||
|
self.annotations = annotations if annotations is not None else {}
|
||||||
self.layer = layer
|
self.layer = layer
|
||||||
self.dose = dose
|
self.dose = dose
|
||||||
self.rotate(rotation)
|
self.rotate(rotation)
|
||||||
[self.mirror(a) for a, do in enumerate(mirrored) if do]
|
[self.mirror(a) for a, do in enumerate(mirrored) if do]
|
||||||
self.locked = locked
|
self.set_locked(locked)
|
||||||
|
|
||||||
def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Polygon':
|
def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Polygon':
|
||||||
memo = {} if memo is None else memo
|
memo = {} if memo is None else memo
|
||||||
new = copy.copy(self).unlock()
|
new = copy.copy(self).unlock()
|
||||||
new._offset = self._offset.copy()
|
new._offset = self._offset.copy()
|
||||||
new._vertices = self._vertices.copy()
|
new._vertices = self._vertices.copy()
|
||||||
new.locked = self.locked
|
new._annotations = copy.deepcopy(self._annotations)
|
||||||
|
new.set_locked(self.locked)
|
||||||
return new
|
return new
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
from typing import List, Tuple, Callable, TypeVar, Optional, TYPE_CHECKING
|
from typing import List, Tuple, Callable, TypeVar, Optional, TYPE_CHECKING
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
import copy
|
import copy
|
||||||
import numpy
|
|
||||||
|
import numpy # type: ignore
|
||||||
|
|
||||||
from ..error import PatternError, PatternLockedError
|
from ..error import PatternError, PatternLockedError
|
||||||
from ..utils import is_scalar, rotation_matrix_2d, vector2, layer_t
|
from ..utils import is_scalar, rotation_matrix_2d, vector2, layer_t
|
||||||
from ..traits import (PositionableImpl, LayerableImpl, DoseableImpl,
|
from ..traits import (PositionableImpl, LayerableImpl, DoseableImpl,
|
||||||
Rotatable, Mirrorable, Copyable, Scalable,
|
Rotatable, Mirrorable, Copyable, Scalable,
|
||||||
PivotableImpl, LockableImpl, RepeatableImpl)
|
PivotableImpl, LockableImpl, RepeatableImpl,
|
||||||
|
AnnotatableImpl)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from . import Polygon
|
from . import Polygon
|
||||||
@ -27,7 +29,7 @@ T = TypeVar('T', bound='Shape')
|
|||||||
|
|
||||||
|
|
||||||
class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable, Copyable, Scalable,
|
class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable, Copyable, Scalable,
|
||||||
PivotableImpl, RepeatableImpl, LockableImpl, metaclass=ABCMeta):
|
PivotableImpl, RepeatableImpl, LockableImpl, AnnotatableImpl, metaclass=ABCMeta):
|
||||||
"""
|
"""
|
||||||
Abstract class specifying functions common to all shapes.
|
Abstract class specifying functions common to all shapes.
|
||||||
"""
|
"""
|
||||||
@ -39,7 +41,7 @@ class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable
|
|||||||
def __copy__(self) -> 'Shape':
|
def __copy__(self) -> 'Shape':
|
||||||
cls = self.__class__
|
cls = self.__class__
|
||||||
new = cls.__new__(cls)
|
new = cls.__new__(cls)
|
||||||
for name in self.__slots__:
|
for name in self.__slots__: # type: str
|
||||||
object.__setattr__(new, name, getattr(self, name))
|
object.__setattr__(new, name, getattr(self, name))
|
||||||
return new
|
return new
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from typing import List, Tuple, Dict, Sequence, Optional, MutableSequence
|
from typing import List, Tuple, Dict, Sequence, Optional, MutableSequence
|
||||||
import copy
|
import copy
|
||||||
import numpy
|
|
||||||
|
import numpy # type: ignore
|
||||||
from numpy import pi, inf
|
from numpy import pi, inf
|
||||||
|
|
||||||
from . import Shape, Polygon, normalized_shape_tuple
|
from . import Shape, Polygon, normalized_shape_tuple
|
||||||
@ -8,6 +9,8 @@ from .. import PatternError
|
|||||||
from ..repetition import Repetition
|
from ..repetition import Repetition
|
||||||
from ..traits import RotatableImpl
|
from ..traits import RotatableImpl
|
||||||
from ..utils import is_scalar, vector2, get_bit, normalize_mirror, layer_t, AutoSlots
|
from ..utils import is_scalar, vector2, get_bit, normalize_mirror, layer_t, AutoSlots
|
||||||
|
from ..utils import annotations_t
|
||||||
|
from ..traits import LockableImpl
|
||||||
|
|
||||||
# Loaded on use:
|
# Loaded on use:
|
||||||
# from freetype import Face
|
# from freetype import Face
|
||||||
@ -67,10 +70,11 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
|
|||||||
layer: layer_t = 0,
|
layer: layer_t = 0,
|
||||||
dose: float = 1.0,
|
dose: float = 1.0,
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
|
annotations: Optional[annotations_t] = None,
|
||||||
locked: bool = False,
|
locked: bool = False,
|
||||||
raw: bool = False,
|
raw: bool = False,
|
||||||
):
|
):
|
||||||
object.__setattr__(self, 'locked', False)
|
LockableImpl.unlock(self)
|
||||||
self.identifier = ()
|
self.identifier = ()
|
||||||
if raw:
|
if raw:
|
||||||
self._offset = offset
|
self._offset = offset
|
||||||
@ -81,6 +85,7 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
|
|||||||
self._rotation = rotation
|
self._rotation = rotation
|
||||||
self._mirrored = mirrored
|
self._mirrored = mirrored
|
||||||
self._repetition = repetition
|
self._repetition = repetition
|
||||||
|
self._annotations = annotations if annotations is not None else {}
|
||||||
else:
|
else:
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
self.layer = layer
|
self.layer = layer
|
||||||
@ -90,15 +95,17 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
|
|||||||
self.rotation = rotation
|
self.rotation = rotation
|
||||||
self.mirrored = mirrored
|
self.mirrored = mirrored
|
||||||
self.repetition = repetition
|
self.repetition = repetition
|
||||||
|
self.annotations = annotations if annotations is not None else {}
|
||||||
self.font_path = font_path
|
self.font_path = font_path
|
||||||
self.locked = locked
|
self.set_locked(locked)
|
||||||
|
|
||||||
def __deepcopy__(self, memo: Dict = None) -> 'Text':
|
def __deepcopy__(self, memo: Dict = None) -> 'Text':
|
||||||
memo = {} if memo is None else memo
|
memo = {} if memo is None else memo
|
||||||
new = copy.copy(self).unlock()
|
new = copy.copy(self).unlock()
|
||||||
new._offset = self._offset.copy()
|
new._offset = self._offset.copy()
|
||||||
new._mirrored = copy.deepcopy(self._mirrored, memo)
|
new._mirrored = copy.deepcopy(self._mirrored, memo)
|
||||||
new.locked = self.locked
|
new._annotations = copy.deepcopy(self._annotations)
|
||||||
|
new.set_locked(self.locked)
|
||||||
return new
|
return new
|
||||||
|
|
||||||
def to_polygons(self,
|
def to_polygons(self,
|
||||||
|
@ -7,14 +7,15 @@
|
|||||||
from typing import Union, List, Dict, Tuple, Optional, Sequence, TYPE_CHECKING, Any
|
from typing import Union, List, Dict, Tuple, Optional, Sequence, TYPE_CHECKING, Any
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
import numpy
|
import numpy # type: ignore
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
||||||
from .error import PatternError, PatternLockedError
|
from .error import PatternError, PatternLockedError
|
||||||
from .utils import is_scalar, rotation_matrix_2d, vector2, AutoSlots
|
from .utils import is_scalar, rotation_matrix_2d, vector2, AutoSlots, annotations_t
|
||||||
from .repetition import Repetition
|
from .repetition import Repetition
|
||||||
from .traits import (PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl,
|
from .traits import (PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl,
|
||||||
Mirrorable, PivotableImpl, Copyable, LockableImpl, RepeatableImpl)
|
Mirrorable, PivotableImpl, Copyable, LockableImpl, RepeatableImpl,
|
||||||
|
AnnotatableImpl)
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -22,7 +23,8 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mirrorable,
|
class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mirrorable,
|
||||||
PivotableImpl, Copyable, RepeatableImpl, LockableImpl, metaclass=AutoSlots):
|
PivotableImpl, Copyable, RepeatableImpl, LockableImpl, AnnotatableImpl,
|
||||||
|
metaclass=AutoSlots):
|
||||||
"""
|
"""
|
||||||
SubPattern provides basic support for nesting Pattern objects within each other, by adding
|
SubPattern provides basic support for nesting Pattern objects within each other, by adding
|
||||||
offset, rotation, scaling, and associated methods.
|
offset, rotation, scaling, and associated methods.
|
||||||
@ -49,8 +51,10 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
|||||||
dose: float = 1.0,
|
dose: float = 1.0,
|
||||||
scale: float = 1.0,
|
scale: float = 1.0,
|
||||||
repetition: Optional[Repetition] = None,
|
repetition: Optional[Repetition] = None,
|
||||||
|
annotations: Optional[annotations_t] = None,
|
||||||
locked: bool = False,
|
locked: bool = False,
|
||||||
identifier: Tuple[Any, ...] = ()):
|
identifier: Tuple[Any, ...] = (),
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
pattern: Pattern to reference.
|
pattern: Pattern to reference.
|
||||||
@ -74,7 +78,8 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
|||||||
mirrored = [False, False]
|
mirrored = [False, False]
|
||||||
self.mirrored = mirrored
|
self.mirrored = mirrored
|
||||||
self.repetition = repetition
|
self.repetition = repetition
|
||||||
self.locked = locked
|
self.annotations = annotations if annotations is not None else {}
|
||||||
|
self.set_locked(locked)
|
||||||
|
|
||||||
def __copy__(self) -> 'SubPattern':
|
def __copy__(self) -> 'SubPattern':
|
||||||
new = SubPattern(pattern=self.pattern,
|
new = SubPattern(pattern=self.pattern,
|
||||||
@ -84,6 +89,7 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
|||||||
scale=self.scale,
|
scale=self.scale,
|
||||||
mirrored=self.mirrored.copy(),
|
mirrored=self.mirrored.copy(),
|
||||||
repetition=copy.deepcopy(self.repetition),
|
repetition=copy.deepcopy(self.repetition),
|
||||||
|
annotations=copy.deepcopy(self.annotations),
|
||||||
locked=self.locked)
|
locked=self.locked)
|
||||||
return new
|
return new
|
||||||
|
|
||||||
@ -92,7 +98,8 @@ class SubPattern(PositionableImpl, DoseableImpl, RotatableImpl, ScalableImpl, Mi
|
|||||||
new = copy.copy(self).unlock()
|
new = copy.copy(self).unlock()
|
||||||
new.pattern = copy.deepcopy(self.pattern, memo)
|
new.pattern = copy.deepcopy(self.pattern, memo)
|
||||||
new.repetition = copy.deepcopy(self.repetition, memo)
|
new.repetition = copy.deepcopy(self.repetition, memo)
|
||||||
new.locked = self.locked
|
new.annotations = copy.deepcopy(self.annotations, memo)
|
||||||
|
new.set_locked(self.locked)
|
||||||
return new
|
return new
|
||||||
|
|
||||||
# pattern property
|
# pattern property
|
||||||
|
@ -7,3 +7,4 @@ from .scalable import Scalable, ScalableImpl
|
|||||||
from .mirrorable import Mirrorable
|
from .mirrorable import Mirrorable
|
||||||
from .copyable import Copyable
|
from .copyable import Copyable
|
||||||
from .lockable import Lockable, LockableImpl
|
from .lockable import Lockable, LockableImpl
|
||||||
|
from .annotatable import Annotatable, AnnotatableImpl
|
||||||
|
56
masque/traits/annotatable.py
Normal file
56
masque/traits/annotatable.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
from typing import TypeVar
|
||||||
|
from types import MappingProxyType
|
||||||
|
from abc import ABCMeta, abstractmethod
|
||||||
|
import copy
|
||||||
|
|
||||||
|
from ..utils import annotations_t
|
||||||
|
from ..error import PatternError
|
||||||
|
|
||||||
|
|
||||||
|
T = TypeVar('T', bound='Annotatable')
|
||||||
|
I = TypeVar('I', bound='AnnotatableImpl')
|
||||||
|
|
||||||
|
|
||||||
|
class Annotatable(metaclass=ABCMeta):
|
||||||
|
"""
|
||||||
|
Abstract class for all annotatable entities
|
||||||
|
Annotations correspond to GDS/OASIS "properties"
|
||||||
|
"""
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
'''
|
||||||
|
---- Properties
|
||||||
|
'''
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def annotations(self) -> annotations_t:
|
||||||
|
"""
|
||||||
|
Dictionary mapping annotation names to values
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AnnotatableImpl(Annotatable, metaclass=ABCMeta):
|
||||||
|
"""
|
||||||
|
Simple implementation of `Annotatable`.
|
||||||
|
"""
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
_annotations: annotations_t
|
||||||
|
""" Dictionary storing annotation name/value pairs """
|
||||||
|
|
||||||
|
'''
|
||||||
|
---- Non-abstract properties
|
||||||
|
'''
|
||||||
|
@property
|
||||||
|
def annotations(self) -> annotations_t:
|
||||||
|
# TODO: Find a way to make sure the subclass implements Lockable without dealing with diamond inheritance or this extra hasattr
|
||||||
|
if hasattr(self, 'is_locked') and self.is_locked():
|
||||||
|
return MappingProxyType(self._annotations)
|
||||||
|
return self._annotations
|
||||||
|
|
||||||
|
@annotations.setter
|
||||||
|
def annotations(self, annotations: annotations_t):
|
||||||
|
if not isinstance(annotations, dict):
|
||||||
|
raise PatternError(f'annotations expected dict, got {type(annotations)}')
|
||||||
|
self._annotations = annotations
|
@ -1,7 +1,6 @@
|
|||||||
from typing import List, Tuple, Callable, TypeVar, Optional
|
from typing import List, Tuple, Callable, TypeVar, Optional
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
import copy
|
import copy
|
||||||
import numpy
|
|
||||||
|
|
||||||
from ..error import PatternError, PatternLockedError
|
from ..error import PatternError, PatternLockedError
|
||||||
from ..utils import is_scalar
|
from ..utils import is_scalar
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
from typing import List, Tuple, Callable, TypeVar, Optional
|
from typing import List, Tuple, Callable, TypeVar, Optional
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
import copy
|
import copy
|
||||||
import numpy
|
|
||||||
|
|
||||||
from ..error import PatternError, PatternLockedError
|
from ..error import PatternError, PatternLockedError
|
||||||
from ..utils import layer_t
|
from ..utils import layer_t
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
from typing import List, Tuple, Callable, TypeVar, Optional
|
from typing import List, Tuple, Callable, TypeVar, Optional
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
import copy
|
import copy
|
||||||
import numpy
|
|
||||||
|
|
||||||
from ..error import PatternError, PatternLockedError
|
from ..error import PatternError, PatternLockedError
|
||||||
|
|
||||||
@ -19,6 +18,7 @@ class Lockable(metaclass=ABCMeta):
|
|||||||
'''
|
'''
|
||||||
---- Methods
|
---- Methods
|
||||||
'''
|
'''
|
||||||
|
@abstractmethod
|
||||||
def lock(self: T) -> T:
|
def lock(self: T) -> T:
|
||||||
"""
|
"""
|
||||||
Lock the object, disallowing further changes
|
Lock the object, disallowing further changes
|
||||||
@ -28,6 +28,7 @@ class Lockable(metaclass=ABCMeta):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
def unlock(self: T) -> T:
|
def unlock(self: T) -> T:
|
||||||
"""
|
"""
|
||||||
Unlock the object, reallowing changes
|
Unlock the object, reallowing changes
|
||||||
@ -37,6 +38,32 @@ class Lockable(metaclass=ABCMeta):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def is_locked(self) -> bool:
|
||||||
|
"""
|
||||||
|
Returns:
|
||||||
|
True if the object is locked
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_locked(self: T, locked: bool) -> T:
|
||||||
|
"""
|
||||||
|
Locks or unlocks based on the argument.
|
||||||
|
No action if already in the requested state.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
locked: State to set.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
|
"""
|
||||||
|
if locked != self.is_locked():
|
||||||
|
if locked:
|
||||||
|
self.lock()
|
||||||
|
else:
|
||||||
|
self.unlock()
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
class LockableImpl(Lockable, metaclass=ABCMeta):
|
class LockableImpl(Lockable, metaclass=ABCMeta):
|
||||||
"""
|
"""
|
||||||
@ -62,3 +89,6 @@ class LockableImpl(Lockable, metaclass=ABCMeta):
|
|||||||
def unlock(self: I) -> I:
|
def unlock(self: I) -> I:
|
||||||
object.__setattr__(self, 'locked', False)
|
object.__setattr__(self, 'locked', False)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def is_locked(self) -> bool:
|
||||||
|
return self.locked
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
from typing import List, Tuple, Callable, TypeVar, Optional
|
from typing import List, Tuple, Callable, TypeVar, Optional
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
import copy
|
import copy
|
||||||
import numpy
|
|
||||||
|
|
||||||
from ..error import PatternError, PatternLockedError
|
from ..error import PatternError, PatternLockedError
|
||||||
|
|
||||||
|
|
||||||
T = TypeVar('T', bound='Mirrorable')
|
T = TypeVar('T', bound='Mirrorable')
|
||||||
#I = TypeVar('I', bound='MirrorableImpl')
|
#I = TypeVar('I', bound='MirrorableImpl')
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
from typing import List, Tuple, Callable, TypeVar, Optional
|
from typing import List, Tuple, Callable, TypeVar, Optional
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
import copy
|
import copy
|
||||||
import numpy
|
import numpy # type: ignore
|
||||||
|
|
||||||
from ..error import PatternError, PatternLockedError
|
from ..error import PatternError, PatternLockedError
|
||||||
from ..utils import is_scalar, rotation_matrix_2d, vector2
|
from ..utils import is_scalar, rotation_matrix_2d, vector2
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
from typing import List, Tuple, Callable, TypeVar, Optional, TYPE_CHECKING
|
from typing import List, Tuple, Callable, TypeVar, Optional, TYPE_CHECKING
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
import copy
|
import copy
|
||||||
import numpy
|
|
||||||
|
|
||||||
from ..error import PatternError, PatternLockedError
|
from ..error import PatternError, PatternLockedError
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ from typing import List, Tuple, Callable, TypeVar, Optional
|
|||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
import numpy
|
import numpy # type: ignore
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
||||||
from .positionable import Positionable
|
from .positionable import Positionable
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
from typing import List, Tuple, Callable, TypeVar, Optional
|
from typing import List, Tuple, Callable, TypeVar, Optional
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
import copy
|
import copy
|
||||||
import numpy
|
|
||||||
|
|
||||||
from ..error import PatternError, PatternLockedError
|
from ..error import PatternError, PatternLockedError
|
||||||
from ..utils import is_scalar
|
from ..utils import is_scalar
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
"""
|
"""
|
||||||
Various helper functions
|
Various helper functions
|
||||||
"""
|
"""
|
||||||
|
from typing import Any, Union, Tuple, Sequence, Dict, List
|
||||||
from typing import Any, Union, Tuple, Sequence
|
|
||||||
from abc import ABCMeta
|
from abc import ABCMeta
|
||||||
|
|
||||||
import numpy
|
import numpy # type: ignore
|
||||||
|
|
||||||
|
|
||||||
# Type definitions
|
# Type definitions
|
||||||
vector2 = Union[numpy.ndarray, Tuple[float, float], Sequence[float]]
|
vector2 = Union[numpy.ndarray, Tuple[float, float], Sequence[float]]
|
||||||
layer_t = Union[int, Tuple[int, int], str]
|
layer_t = Union[int, Tuple[int, int], str]
|
||||||
|
annotations_t = Dict[str, List[Union[int, float, str]]]
|
||||||
|
|
||||||
|
|
||||||
def is_scalar(var: Any) -> bool:
|
def is_scalar(var: Any) -> bool:
|
||||||
|
Loading…
Reference in New Issue
Block a user