add support for annotations
and other fixes
This commit is contained in:
		
							parent
							
								
									c611699fc8
								
							
						
					
					
						commit
						73c9050138
					
				| @ -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,13 +264,12 @@ 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).') | ||||||
|     return pat |     return pat | ||||||
|  | |||||||
| @ -11,17 +11,18 @@ Note that GDSII references follow the same convention as `masque`, | |||||||
|   Scaling, rotation, and mirroring apply to individual instances, not grid |   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,21 +494,20 @@ 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: | ||||||
|         if subpat.pattern is None: |         if subpat.pattern is None: | ||||||
| @ -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.layer = layer |             self._radius = radius | ||||||
|         self.dose = dose |             self._offset = offset | ||||||
|         self.radius = radius |             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.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.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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user