From 705926d44345d0d9c9e494ddc6864a115a21ea34 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Tue, 19 May 2020 00:42:42 -0700 Subject: [PATCH] Add UnfilledModalError, records.verify_modal(), and .get_*() methods. The .get_*() methods are used to verify that we aren't reading from a pattern with un-filled modals. The GeometryMixin class was also added here and provides some additional convenience methods: get_xy() to get an (x,y) tuple and get_layer_tuple() to get a (layer, datatype) tuple. --- fatamorgana/__init__.py | 1 + fatamorgana/basic.py | 7 ++- fatamorgana/records.py | 135 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 131 insertions(+), 12 deletions(-) diff --git a/fatamorgana/__init__.py b/fatamorgana/__init__.py index b56fb99..1ffb62a 100644 --- a/fatamorgana/__init__.py +++ b/fatamorgana/__init__.py @@ -29,6 +29,7 @@ import pathlib from .main import OasisLayout, Cell, XName from .basic import NString, AString, Validation, OffsetTable, OffsetEntry, \ EOFError, SignedError, InvalidDataError, InvalidRecordError, \ + UnfilledModalError, \ ReuseRepetition, GridRepetition, ArbitraryRepetition diff --git a/fatamorgana/basic.py b/fatamorgana/basic.py index df1513d..218f80c 100644 --- a/fatamorgana/basic.py +++ b/fatamorgana/basic.py @@ -59,6 +59,12 @@ class InvalidRecordError(FatamorganaError): """ pass +class UnfilledModalError(FatamorganaError): + """ + Attempted to call .get_var(), but var() was None! + """ + pass + class PathExtensionScheme(Enum): """ @@ -2228,4 +2234,3 @@ def read_magic_bytes(stream: io.BufferedIOBase): if magic != MAGIC_BYTES: raise InvalidDataError('Could not read magic bytes, ' 'found {!r} : {}'.format(magic, magic.decode())) - diff --git a/fatamorgana/records.py b/fatamorgana/records.py index 3ce18f0..490b22c 100644 --- a/fatamorgana/records.py +++ b/fatamorgana/records.py @@ -11,7 +11,7 @@ Higher-level code (e.g. monitoring for combinations of records with in main.py instead. """ from abc import ABCMeta, abstractmethod -from typing import List, Dict, Tuple, Union, Optional, Sequence, Any +from typing import List, Dict, Tuple, Union, Optional, Sequence, Any, TypeVar import copy import math import zlib @@ -24,7 +24,7 @@ from .basic import AString, NString, repetition_t, property_value_t, real_t, \ read_bstring, read_uint, read_sint, read_real, read_repetition, read_interval, \ write_bstring, write_uint, write_sint, write_real, write_interval, write_point_list, \ write_property_value, read_bool_byte, write_bool_byte, read_byte, write_byte, \ - InvalidDataError, PathExtensionScheme, _USE_NUMPY + InvalidDataError, UnfilledModalError, PathExtensionScheme, _USE_NUMPY if _USE_NUMPY: import numpy @@ -32,6 +32,7 @@ if _USE_NUMPY: logger = logging.getLogger(__name__) + ''' Type definitions ''' @@ -112,6 +113,12 @@ class Modals: self.property_is_standard = None +T = TypeVar('T') +def verify_modal(var: Optional[T]) -> T: + if var is None: + raise UnfilledModalError + return var + ''' Records @@ -217,6 +224,34 @@ class Record(metaclass=ABCMeta): return '{}: {}'.format(self.__class__, pprint.pformat(self.__dict__)) +class GeometryMixin(metaclass=ABCMeta): + """ + Mixin defining common functions for geometry records + """ + x: Optional[int] + y: Optional[int] + layer: Optional[int] + datatype: Optional[int] + + def get_x(self) -> int: + return verify_modal(self.x) + + def get_y(self) -> int: + return verify_modal(self.y) + + def get_xy(self) -> Tuple[int, int]: + return (self.get_x(), self.get_y()) + + def get_layer(self) -> int: + return verify_modal(self.layer) + + def get_datatype(self) -> int: + return verify_modal(self.datatype) + + def get_layer_tuple(self) -> Tuple[int, int]: + return (self.get_layer(), self.get_datatype()) + + def read_refname(stream: io.BufferedIOBase, is_present: Union[bool, int], is_reference: Union[bool, int] @@ -910,6 +945,15 @@ class Property(Record): self.values = values self.is_standard = is_standard + def get_name(self) -> Union[NString, int]: + return verify_modal(self.name) # type: ignore + + def get_values(self) -> List[property_value_t]: + return verify_modal(self.values) + + def get_is_standard(self) -> bool: + return verify_modal(self.is_standard) + def merge_with_modals(self, modals: Modals): adjust_field(self, 'name', modals, 'property_name') adjust_field(self, 'values', modals, 'property_value_list') @@ -1091,7 +1135,7 @@ class XElement(Record): return size -class XGeometry(Record): +class XGeometry(Record, GeometryMixin): """ XGeometry record (ID 33) @@ -1301,6 +1345,15 @@ class Placement(Record): else: self.name = name + def get_name(self) -> Union[NString, int]: + return verify_modal(self.name) # type: ignore + + def get_x(self) -> int: + return verify_modal(self.x) + + def get_y(self) -> int: + return verify_modal(self.y) + def merge_with_modals(self, modals: Modals): adjust_coordinates(self, modals, 'placement_x', 'placement_y') adjust_repetition(self, modals) @@ -1384,7 +1437,7 @@ class Placement(Record): return size -class Text(Record): +class Text(Record, GeometryMixin): """ Text record (ID 19) @@ -1430,6 +1483,9 @@ class Text(Record): else: self.string = string + def get_string(self) -> Union[AString, int]: + return verify_modal(self.string) # type: ignore + def merge_with_modals(self, modals: Modals): adjust_coordinates(self, modals, 'text_x', 'text_y') adjust_repetition(self, modals) @@ -1500,7 +1556,7 @@ class Text(Record): return size -class Rectangle(Record): +class Rectangle(Record, GeometryMixin): """ Rectangle record (ID 20) @@ -1558,6 +1614,14 @@ class Rectangle(Record): if is_square and self.height is not None: raise InvalidDataError('Rectangle is square and also has height') + def get_width(self) -> int: + return verify_modal(self.width) + + def get_height(self) -> int: + if self.is_square: + return verify_modal(self.width) + return verify_modal(self.height) + def merge_with_modals(self, modals: Modals): adjust_coordinates(self, modals, 'geometry_x', 'geometry_y') adjust_repetition(self, modals) @@ -1635,7 +1699,7 @@ class Rectangle(Record): return size -class Polygon(Record): +class Polygon(Record, GeometryMixin): """ Polygon record (ID 21) @@ -1685,6 +1749,9 @@ class Polygon(Record): if len(point_list) < 3: warn('Polygon with < 3 points') + def get_point_list(self) -> point_list_t: + return verify_modal(self.point_list) + def merge_with_modals(self, modals: Modals): adjust_coordinates(self, modals, 'geometry_x', 'geometry_y') adjust_repetition(self, modals) @@ -1752,7 +1819,7 @@ class Polygon(Record): return size -class Path(Record): +class Path(Record, GeometryMixin): """ Polygon record (ID 22) @@ -1821,6 +1888,18 @@ class Path(Record): self.extension_start = extension_start self.extension_end = extension_end + def get_point_list(self) -> point_list_t: + return verify_modal(self.point_list) + + def get_half_width(self) -> int: + return verify_modal(self.half_width) + + def get_extension_start(self) -> pathextension_t: + return verify_modal(self.extension_start) + + def get_extension_end(self) -> pathextension_t: + return verify_modal(self.extension_end) + def merge_with_modals(self, modals: Modals): adjust_coordinates(self, modals, 'geometry_x', 'geometry_y') adjust_repetition(self, modals) @@ -1927,7 +2006,7 @@ class Path(Record): return size -class Trapezoid(Record): +class Trapezoid(Record, GeometryMixin): """ Trapezoid record (ID 23, 24, 25) @@ -2017,6 +2096,21 @@ class Trapezoid(Record): raise InvalidDataError('Trapezoid: w < delta_b - delta_a' ' ({} < {} - {})'.format(width, delta_b, delta_a)) + def get_is_vertical(self) -> bool: + return verify_modal(self.is_vertical) + + def get_delta_a(self) -> int: + return verify_modal(self.delta_a) + + def get_delta_b(self) -> int: + return verify_modal(self.delta_b) + + def get_width(self) -> int: + return verify_modal(self.width) + + def get_height(self) -> int: + return verify_modal(self.height) + def merge_with_modals(self, modals: Modals): adjust_coordinates(self, modals, 'geometry_x', 'geometry_y') adjust_repetition(self, modals) @@ -2103,7 +2197,7 @@ class Trapezoid(Record): # TODO: CTrapezoid type descriptions -class CTrapezoid(Record): +class CTrapezoid(Record, GeometryMixin): """ CTrapezoid record (ID 26) @@ -2161,6 +2255,23 @@ class CTrapezoid(Record): self.check_valid() + def get_ctrapezoid_type(self) -> int: + return verify_modal(self.ctrapezoid_type) + + def get_height(self) -> int: + if self.ctrapezoid_type is None: + return verify_modal(self.height) + if self.ctrapezoid_type in (16, 17, 18, 19, 22, 23, 25): + return verify_modal(self.width) + return verify_modal(self.height) + + def get_width(self) -> int: + if self.ctrapezoid_type is None: + return verify_modal(self.width) + if self.ctrapezoid_type in (20, 21): + return verify_modal(self.height) + return verify_modal(self.width) + def merge_with_modals(self, modals: Modals): adjust_coordinates(self, modals, 'geometry_x', 'geometry_y') adjust_repetition(self, modals) @@ -2267,7 +2378,6 @@ class CTrapezoid(Record): size += self.repetition.write(stream) # type: ignore return size - def check_valid(self): ctrapezoid_type = self.ctrapezoid_type width = self.width @@ -2299,7 +2409,7 @@ class CTrapezoid(Record): '{}'.format(ctrapezoid_type)) -class Circle(Record): +class Circle(Record, GeometryMixin): """ Circle record (ID 27) @@ -2344,6 +2454,9 @@ class Circle(Record): self.y = y self.repetition = repetition + def get_radius(self) -> int: + return verify_modal(self.radius) + def merge_with_modals(self, modals: Modals): adjust_coordinates(self, modals, 'geometry_x', 'geometry_y') adjust_repetition(self, modals)