diff --git a/.flake8 b/.flake8 deleted file mode 100644 index fb07707..0000000 --- a/.flake8 +++ /dev/null @@ -1,30 +0,0 @@ -[flake8] -ignore = - # E501 line too long - E501, - # W391 newlines at EOF - W391, - # E241 multiple spaces after comma - E241, - # E302 expected 2 newlines - E302, - # W503 line break before binary operator (to be deprecated) - W503, - # E265 block comment should start with '# ' - E265, - # E123 closing bracket does not match indentation of opening bracket's line - E123, - # E124 closing bracket does not match visual indentation - E124, - # E221 multiple spaces before operator - E221, - # E201 whitespace after '[' - E201, - # E741 ambiguous variable name 'I' - E741, - - -per-file-ignores = - # F401 import without use - */__init__.py: F401, - __init__.py: F401, diff --git a/.gitignore b/.gitignore index 0991b1f..f0e9ec8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,7 @@ *.pyc __pycache__ *.idea -.mypy_cache/ build dist fatamorgana.egg-info -docs - -*.gds -*.gds.gz -*.oas -*.oas.gz diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..c28ab72 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include README.md +include LICENSE.md diff --git a/README.md b/README.md index d4df9bb..92325cd 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,12 @@ -# fatamorgana +# fatamorgana **fatamorgana** is a Python package for reading and writing OASIS format layout files. **Homepage:** https://mpxd.net/code/jan/fatamorgana -* [PyPI](https://pypi.org/project/fatamorgana) **Capabilities:** * This package is a work-in-progress and is largely untested -- it works for - the tasks I usually use it for, but I can't guarantee I've even + the tasks I usually use it for, but I can't guarantee I've even tried the features you happen to use! Use at your own risk! * Interfaces and datastructures are subject to change! * That said the following work for me: @@ -21,7 +20,7 @@ ## Installation **Dependencies:** -* python >=3.11 +* python 3.5 or newer * (optional) numpy @@ -30,6 +29,11 @@ Install with pip from PyPi (preferred): pip install fatamorgana ``` +Install directly from git repository: +```bash +pip install git+https://mpxd.net/code/jan/fatamorgana.git@release +``` + ## Documentation Most functions and classes are documented inline. @@ -38,6 +42,7 @@ To read the inline help, import fatamorgana help(fatamorgana.OasisLayout) ``` +The documentation is currently very sparse and I expect to improve it whenever possible! ## Examples @@ -48,7 +53,7 @@ Read an OASIS file and write it back out: with open('test.oas', 'rb') as f: layout = fatamorgana.OasisLayout.read(f) - + with open('test_write.oas', 'wb') as f: layout.write(f) ``` diff --git a/fatamorgana/LICENSE.md b/fatamorgana/LICENSE.md deleted file mode 120000 index 7eabdb1..0000000 --- a/fatamorgana/LICENSE.md +++ /dev/null @@ -1 +0,0 @@ -../LICENSE.md \ No newline at end of file diff --git a/fatamorgana/README.md b/fatamorgana/README.md deleted file mode 120000 index 32d46ee..0000000 --- a/fatamorgana/README.md +++ /dev/null @@ -1 +0,0 @@ -../README.md \ No newline at end of file diff --git a/fatamorgana/__init__.py b/fatamorgana/__init__.py index 27c9c74..0e8485b 100644 --- a/fatamorgana/__init__.py +++ b/fatamorgana/__init__.py @@ -15,37 +15,14 @@ numpy to speed up reading/writing. Dependencies: - - Python 3.11 or later - - numpy (optional, faster but no additional functionality) - - To get started, try: - ```python3 - import fatamorgana - help(fatamorgana.OasisLayout) - ``` + - Python 3.5 or later + - numpy (optional, no additional functionality) """ -from .main import ( - OasisLayout as OasisLayout, - Cell as Cell, - XName as XName, - ) -from .basic import ( - NString as NString, - AString as AString, - Validation as Validation, - OffsetTable as OffsetTable, - OffsetEntry as OffsetEntry, - EOFError as EOFError, - SignedError as SignedError, - InvalidDataError as InvalidDataError, - InvalidRecordError as InvalidRecordError, - UnfilledModalError as UnfilledModalError, - ReuseRepetition as ReuseRepetition, - GridRepetition as GridRepetition, - ArbitraryRepetition as ArbitraryRepetition, - ) +from .main import OasisLayout, Cell, XName +from .basic import NString, AString, Validation, OffsetTable, OffsetEntry, \ + EOFError, SignedError, InvalidDataError, InvalidRecordError __author__ = 'Jan Petykiewicz' -__version__ = '0.13' -version = __version__ + +version = '0.4' diff --git a/fatamorgana/basic.py b/fatamorgana/basic.py index 70da126..4db5ba0 100644 --- a/fatamorgana/basic.py +++ b/fatamorgana/basic.py @@ -2,29 +2,26 @@ This module contains all datatypes and parsing/writing functions for all abstractions below the 'record' or 'block' level. """ -from typing import Any, IO, Union, TYPE_CHECKING -from collections.abc import Sequence from fractions import Fraction +from typing import List, Tuple, Type from enum import Enum import math import struct -import warnings +import io try: import numpy - from numpy.typing import NDArray _USE_NUMPY = True -except ImportError: +except: _USE_NUMPY = False ''' Type definitions ''' -real_t = int | float | Fraction -repetition_t = Union['ReuseRepetition', 'GridRepetition', 'ArbitraryRepetition'] -property_value_t = Union[int, bytes, 'AString', 'NString', 'PropStringReference', float, Fraction] -bytes_t = bytes +real_t = int or float or Fraction +repetition_t = 'ReuseRepetition' or 'GridRepetition' or 'ArbitraryRepetition' +property_value_t = int or bytes or 'AString' or 'NString' or' PropStringReference' or float or Fraction class FatamorganaError(Exception): @@ -61,12 +58,6 @@ class InvalidRecordError(FatamorganaError): """ pass -class UnfilledModalError(FatamorganaError): - """ - Attempted to call .get_var(), but var() was None! - """ - pass - class PathExtensionScheme(Enum): """ @@ -77,29 +68,25 @@ class PathExtensionScheme(Enum): Arbitrary = 3 + ''' Constants ''' -MAGIC_BYTES: bytes = b'%SEMI-OASIS\r\n' +MAGIC_BYTES = b'%SEMI-OASIS\r\n' # type: bytes ''' Basic IO ''' -def _read(stream: IO[bytes], n: int) -> bytes: +def _read(stream: io.BufferedIOBase, n: int) -> bytes: """ Read n bytes from the stream. Raise an EOFError if there were not enough bytes in the stream. - Args: - stream: Stream to read from. - n: Number of bytes to read. - - Returns: - The bytes that were read. - - Raises: - EOFError if not enough bytes could be read. + :param stream: Stream to read from. + :param n: Number of bytes to read. + :return: The bytes that were read. + :raises: EOFError if not enough bytes could be read. """ b = stream.read(n) if len(b) != n: @@ -107,109 +94,81 @@ def _read(stream: IO[bytes], n: int) -> bytes: return b -def read_byte(stream: IO[bytes]) -> int: +def read_byte(stream: io.BufferedIOBase) -> int: """ Read a single byte and return it. - Args: - stream: Stream to read from. - - Returns: - The byte that was read. + :param stream: Stream to read from. + :return: The byte that was read. """ return _read(stream, 1)[0] -def write_byte(stream: IO[bytes], n: int) -> int: +def write_byte(stream: io.BufferedIOBase, n: int) -> int: """ Write a single byte to the stream. - Args: - stream: Stream to read from. - - Returns: - The number of bytes writen (1). + :param stream: Stream to read from. + :return: The number of bytes writen (1). """ return stream.write(bytes((n,))) -def _py_read_bool_byte(stream: IO[bytes]) -> list[bool]: - """ - Read a single byte from the stream, and interpret its bits as - a list of 8 booleans. - - Args: - stream: Stream to read from. - - Returns: - A list of 8 booleans corresponding to the bits (MSB first). - """ - byte = _read(stream, 1)[0] - bits = [bool((byte >> i) & 0x01) for i in reversed(range(8))] - return bits - -def _py_write_bool_byte(stream: IO[bytes], bits: tuple[bool | int, ...]) -> int: - """ - Pack 8 booleans into a byte, and write it to the stream. - - Args: - stream: Stream to write to. - bits: A list of 8 booleans corresponding to the bits (MSB first). - - Returns: - Number of bytes written (1). - - Raises: - InvalidDataError if didn't receive 8 bits. - """ - if len(bits) != 8: - raise InvalidDataError(f'write_bool_byte received {len(bits)} bits, requires 8') - byte = 0 - for i, bit in enumerate(reversed(bits)): - byte |= bit << i - return stream.write(bytes((byte,))) - - if _USE_NUMPY: - def _np_read_bool_byte(stream: IO[bytes]) -> NDArray[numpy.uint8]: + def read_bool_byte(stream: io.BufferedIOBase) -> List[bool]: """ Read a single byte from the stream, and interpret its bits as a list of 8 booleans. - Args: - stream: Stream to read from. - - Returns: - A list of 8 booleans corresponding to the bits (MSB first). + :param stream: Stream to read from. + :return: A list of 8 booleans corresponding to the bits (MSB first). """ byte_arr = _read(stream, 1) return numpy.unpackbits(numpy.frombuffer(byte_arr, dtype=numpy.uint8)) - def _np_write_bool_byte(stream: IO[bytes], bits: tuple[bool | int, ...]) -> int: + def write_bool_byte(stream: io.BufferedIOBase, bits: Tuple[bool]) -> int: """ Pack 8 booleans into a byte, and write it to the stream. - Args: - stream: Stream to write to. - bits: A list of 8 booleans corresponding to the bits (MSB first). - - Returns: - Number of bytes written (1). - - Raises: - InvalidDataError if didn't receive 8 bits. + :param stream: Stream to write to. + :param bits: A list of 8 booleans corresponding to the bits (MSB first). + :return: Number of bytes written (1). + :raises: InvalidDataError if didn't receive 8 bits. """ if len(bits) != 8: - raise InvalidDataError(f'write_bool_byte received {len(bits)} bits, requires 8') - return stream.write(numpy.packbits(bits)[0]) - - read_bool_byte = _np_read_bool_byte # type: ignore - write_bool_byte = _np_write_bool_byte + raise InvalidDataError('write_bool_byte received {} bits, requires 8'.format(len(bits))) + return stream.write(numpy.packbits(bits)) else: - read_bool_byte = _py_read_bool_byte # type: ignore - write_bool_byte = _py_write_bool_byte + def read_bool_byte(stream: io.BufferedIOBase) -> List[bool]: + """ + Read a single byte from the stream, and interpret its bits as + a list of 8 booleans. -def read_uint(stream: IO[bytes]) -> int: + :param stream: Stream to read from. + :return: A list of 8 booleans corresponding to the bits (MSB first). + """ + byte = _read(1)[0] + bits = [(byte >> i) & 0x01 for i in reversed(range(8))] + return bits + + def write_bool_byte(stream: io.BufferedIOBase, bits: Tuple[bool]) -> int: + """ + Pack 8 booleans into a byte, and write it to the stream. + + :param stream: Stream to write to. + :param bits: A list of 8 booleans corresponding to the bits (MSB first). + :return: Number of bytes written (1). + :raises: InvalidDataError if didn't receive 8 bits. + """ + if len(bits) != 8: + raise InvalidDataError('write_bool_byte received {} bits, requires 8'.format(len(bits))) + byte = 0 + for i, bit in enumerate(reversed(bits)): + byte |= bit << i + return stream.write(bytes((byte))) + + +def read_uint(stream: io.BufferedIOBase) -> int: """ Read an unsigned integer from the stream. @@ -218,11 +177,8 @@ def read_uint(stream: IO[bytes]) -> int: - Remaining bits of each byte form the binary representation of the integer, but are stored _least significant group first_. - Args: - stream: Stream to read from. - - Returns: - The integer's value. + :param stream: Stream to read from. + :return: The integer's value. """ result = 0 i = 0 @@ -235,24 +191,18 @@ def read_uint(stream: IO[bytes]) -> int: return result -def write_uint(stream: IO[bytes], n: int) -> int: +def write_uint(stream: io.BufferedIOBase, n: int) -> int: """ Write an unsigned integer to the stream. - See format details in `read_uint()`. + See format details in read_uint(...). - Args: - stream: Stream to write to. - n: Value to write. - - Returns: - The number of bytes written. - - Raises: - SignedError: if `n` is negative. + :param stream: Stream to write to. + :param n: Value to write. + :return: The number of bytes written. + :raises: SignedError if n is negative. """ - n = int(n) if n < 0: - raise SignedError(f'uint must be positive: {n}') + raise SignedError('uint must be positive: {}'.format(n)) current = n byte_list = [] @@ -276,11 +226,8 @@ def decode_sint(uint: int) -> int: - The LSB is treated as the sign bit - The remainder of the bits encodes the absolute value - Args: - uint: Unsigned integer to decode from. - - Returns: - The decoded signed integer. + :param uint: Unsigned integer to decode from. + :return: The decoded signed integer. """ return (uint >> 1) * (1 - 2 * (0x01 & uint)) @@ -288,179 +235,143 @@ def decode_sint(uint: int) -> int: def encode_sint(sint: int) -> int: """ Encode a signed integer into its corresponding unsigned integer form. - See `decode_sint()` for format details. + See decode_sint() for format details. - Args: - int: The signed integer to encode. - - Returns: - Unsigned integer encoding for the input. + :param int: The signed integer to encode. + :return: Unsigned integer encoding for the input. """ - sint = int(sint) return (abs(sint) << 1) | (sint < 0) -def read_sint(stream: IO[bytes]) -> int: +def read_sint(stream: io.BufferedIOBase) -> int: """ Read a signed integer from the stream. - See `decode_sint()` for format details. + See decode_sint() for format details. - Args: - stream: Stream to read from. - - Returns: - The integer's value. + :param stream: Stream to read from. + :return: The integer's value. """ return decode_sint(read_uint(stream)) -def write_sint(stream: IO[bytes], n: int) -> int: +def write_sint(stream: io.BufferedIOBase, n: int) -> int: """ Write a signed integer to the stream. - See `decode_sint()` for format details. + See decode_sint() for format details. - Args: - stream: Stream to write to. - n: Value to write. - - Returns: - The number of bytes written. + :param stream: Stream to write to. + :param n: Value to write. + :return: The number of bytes written. """ return write_uint(stream, encode_sint(n)) -def read_bstring(stream: IO[bytes]) -> bytes: +def read_bstring(stream: io.BufferedIOBase) -> bytes: """ Read a binary string from the stream. The format is: - length: uint - data: bytes - Args: - stream: Stream to read from. - - Returns: - Bytes containing the binary string. + :param stream: Stream to read from. + :return: Bytes containing the binary string. """ length = read_uint(stream) return _read(stream, length) -def write_bstring(stream: IO[bytes], bstring: bytes) -> int: +def write_bstring(stream: io.BufferedIOBase, bstring: bytes): """ Write a binary string to the stream. - See `read_bstring()` for format details. + See read_bstring() for format details. - Args: - stream: Stream to write to. - bstring: Binary string to write. - - Returns: - The number of bytes written. + :param stream: Stream to write to. + :param bstring: Binary string to write. + :return: The number of bytes written. """ write_uint(stream, len(bstring)) return stream.write(bstring) -def read_ratio(stream: IO[bytes]) -> Fraction: +def read_ratio(stream: io.BufferedIOBase) -> Fraction: """ Read a ratio (unsigned) from the stream. The format is: - numerator: uint - denominator: uint - Args: - stream: Stream to read from. - - Returns: - Fraction object containing the read value. + :param stream: Stream to read from. + :return: Fraction object containing the read value. """ numer = read_uint(stream) denom = read_uint(stream) return Fraction(numer, denom) -def write_ratio(stream: IO[bytes], r: Fraction) -> int: +def write_ratio(stream: io.BufferedIOBase, r: Fraction) -> int: """ Write an unsigned ratio to the stream. - See `read_ratio()` for format details. + See read_ratio() for format details. - Args: - stream: Stream to write to. - r: Ratio to write (`Fraction` object). - - Returns: - The number of bytes written. - - Raises: - SignedError: if r is negative. + :param stream: Stream to write to. + :param r: Ratio to write (Fraction object). + :return: The number of bytes written. + :raises: SignedError if r is negative. """ if r < 0: - raise SignedError(f'Ratio must be unsigned: {r}') + raise SignedError('Ratio must be unsigned: {}'.format(r)) size = write_uint(stream, r.numerator) size += write_uint(stream, r.denominator) return size -def read_float32(stream: IO[bytes]) -> float: +def read_float32(stream: io.BufferedIOBase) -> float: """ Read a 32-bit float from the stream. - Args: - stream: Stream to read from. - - Returns: - The value read. + :param stream: Stream to read from. + :return: The value read. """ b = _read(stream, 4) return struct.unpack(" int: +def write_float32(stream: io.BufferedIOBase, f: float) -> int: """ Write a 32-bit float to the stream. - Arsg: - stream: Stream to write to. - f: Value to write. - - Returns: - The number of bytes written (4). + :param stream: Stream to write to. + :param f: Value to write. + :return: The number of bytes written (4). """ b = struct.pack(" float: +def read_float64(stream: io.BufferedIOBase) -> float: """ Read a 64-bit float from the stream. - Args: - stream: Stream to read from. - - Returns: - The value read. + :param stream: Stream to read from. + :return: The value read. """ b = _read(stream, 8) return struct.unpack(" int: +def write_float64(stream: io.BufferedIOBase, f: float) -> int: """ Write a 64-bit float to the stream. - Args: - stream: Stream to write to. - f: Value to write. - - Returns: - The number of bytes written (8). + :param stream: Stream to write to. + :param f: Value to write. + :return: The number of bytes written (8). """ b = struct.pack(" real_t: +def read_real(stream: io.BufferedIOBase, real_type: int = None) -> real_t: """ Read a real number from the stream. @@ -477,16 +388,11 @@ def read_real(stream: IO[bytes], real_type: int | None = None) -> real_t: 6: 32-bit float 7: 64-bit float - Args: - stream: Stream to read from. - real_type: Type of real number to read. If `None` (default), - the type is read from the stream. - - Returns: - The value read. - - Raises: - InvalidDataError: if real_type is invalid. + :param stream: Stream to read from. + :param real_type: Type of real number to read. If None (default), + the type is read from the stream. + :return: The value read. + :raises: InvalidDataError if real_type is invalid. """ if real_type is None: @@ -507,14 +413,13 @@ def read_real(stream: IO[bytes], real_type: int | None = None) -> real_t: return read_float32(stream) if real_type == 7: return read_float64(stream) - raise InvalidDataError(f'Invalid real type: {real_type}') + raise InvalidDataError('Invalid real type: {}'.format(real_type)) -def write_real( - stream: IO[bytes], - r: real_t, - force_float32: bool = False - ) -> int: +def write_real(stream: io.BufferedIOBase, + r: real_t, + force_float32: bool = False + ) -> int: """ Write a real number to the stream. See read_real() for format details. @@ -525,14 +430,10 @@ def write_real( Since python has no 32-bit floats, the force_float32 parameter will perform the cast at write-time if set to True (default False). - Args: - stream: Stream to write to. - r: Value to write. - force_float32: If `True`, casts r to float32 when writing. - Default `False`. - - Returns: - The number of bytes written. + :param stream: Stream to write to. + :param r: Value to write. + :param float32: + :return: The number of bytes written. """ size = 0 if isinstance(r, int): @@ -560,16 +461,15 @@ class NString: Class for handling "name strings", which hold one or more printable ASCII characters (0x21 to 0x7e, inclusive). - `__init__` can be called with either a string or bytes object; - subsequent reading/writing should use the `string` and - `bytes` properties. + __init__ can be called with either a string or bytes object; + subsequent reading/writing should use the .string and + .bytes properties. """ - _string: str + _string = None # type: str - def __init__(self, string_or_bytes: bytes | str) -> None: + def __init__(self, string_or_bytes: bytes or str): """ - Args: - string_or_bytes: Content of the `NString`. + :param string_or_bytes: Content of the Nstring. """ if isinstance(string_or_bytes, str): self.string = string_or_bytes @@ -581,9 +481,9 @@ class NString: return self._string @string.setter - def string(self, string: str) -> None: + def string(self, string: str): if len(string) == 0 or not all(0x21 <= ord(c) <= 0x7e for c in string): - raise InvalidDataError(f'Invalid n-string {string}') + raise InvalidDataError('Invalid n-string {}'.format(string)) self._string = string @property @@ -591,80 +491,59 @@ class NString: return self._string.encode('ascii') @bytes.setter - def bytes(self, bstring: bytes) -> None: - if len(bstring) == 0 or not all(0x21 <= c <= 0x7e for c in bstring): - raise InvalidDataError(f'Invalid n-string {bstring!r}') + def bytes(self, bstring: bytes): + if len(bstring) == 0 or not all(0x21 <= c <= 0x7e for c in bstring): + raise InvalidDataError('Invalid n-string {}'.format(bstring)) self._string = bstring.decode('ascii') @staticmethod - def read(stream: IO[bytes_t]) -> 'NString': + def read(stream: io.BufferedIOBase) -> 'NString': """ Create an NString object by reading a bstring from the provided stream. - Args: - stream: Stream to read from. - - Returns: - Resulting NString. - - Raises: - InvalidDataError + :param stream: Stream to read from. + :return: Resulting NString. + :raises: InvalidDataError """ return NString(read_bstring(stream)) - def write(self, stream: IO[bytes_t]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: """ Write this NString to a stream. - Args: - stream: Stream to write to. - - Returns: - Number of bytes written. + :param stream: Stream to write to. + :return: Number of bytes written. """ return write_bstring(stream, self.bytes) - def __eq__(self, other: Any) -> bool: + def __eq__(self, other: 'NString') -> bool: return isinstance(other, type(self)) and self.string == other.string def __repr__(self) -> str: return '[N]' + self._string - def __str__(self) -> str: - return self._string - -def read_nstring(stream: IO[bytes]) -> str: +def read_nstring(stream: io.BufferedIOBase) -> str: """ Read a name string from the provided stream. - See `NString` for constraints on name strings. + See NString for constraints on name strings. - Args: - stream: Stream to read from. - - Returns: - Resulting string. - - Raises: - InvalidDataError + :param stream: Stream to read from. + :return: Resulting string. + :raises: InvalidDataError """ return NString.read(stream).string -def write_nstring(stream: IO[bytes], string: str) -> int: +def write_nstring(stream: io.BufferedIOBase, string: str) -> int: """ Write a name string to a stream. - See `NString` for constraints on name strings. + See NString for constraints on name strings. - Args: - stream: Stream to write to. - string: String to write. - - Returns: - Number of bytes written. - - Raises: - InvalidDataError + :param stream: Stream to write to. + :param string: String to write. + :return: Number of bytes written. + :raises: InvalidDataError """ return NString(string).write(stream) @@ -674,16 +553,15 @@ class AString: Class for handling "ascii strings", which hold zero or more ASCII characters (0x20 to 0x7e, inclusive). - `__init__` can be called with either a string or bytes object; - subsequent reading/writing should use the `string` and - `bytes` properties. + __init__ can be called with either a string or bytes object; + subsequent reading/writing should use the .string and + .bytes properties. """ - _string: str + _string = None # type: str - def __init__(self, string_or_bytes: bytes | str) -> None: + def __init__(self, string_or_bytes: bytes or str): """ - Args: - string_or_bytes: Content of the AString. + :param string_or_bytes: Content of the AString. """ if isinstance(string_or_bytes, str): self.string = string_or_bytes @@ -695,9 +573,9 @@ class AString: return self._string @string.setter - def string(self, string: str) -> None: + def string(self, string: str): if not all(0x20 <= ord(c) <= 0x7e for c in string): - raise InvalidDataError(f'Invalid a-string "{string}"') + raise InvalidDataError('Invalid a-string {}'.format(string)) self._string = string @property @@ -705,80 +583,59 @@ class AString: return self._string.encode('ascii') @bytes.setter - def bytes(self, bstring: bytes) -> None: + def bytes(self, bstring: bytes): if not all(0x20 <= c <= 0x7e for c in bstring): - raise InvalidDataError(f'Invalid a-string "{bstring!r}"') + raise InvalidDataError('Invalid a-string {}'.format(bstring)) self._string = bstring.decode('ascii') @staticmethod - def read(stream: IO[bytes_t]) -> 'AString': + def read(stream: io.BufferedIOBase) -> 'AString': """ - Create an `AString` object by reading a bstring from the provided stream. + Create an AString object by reading a bstring from the provided stream. - Args: - stream: Stream to read from. - - Returns: - Resulting `AString`. - - Raises: - InvalidDataError + :param stream: Stream to read from. + :return: Resulting AString. + :raises: InvalidDataError """ return AString(read_bstring(stream)) - def write(self, stream: IO[bytes_t]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: """ - Write this `AString` to a stream. + Write this AString to a stream. - Args: - stream: Stream to write to. - - Returns: - Number of bytes written. + :param stream: Stream to write to. + :return: Number of bytes written. """ return write_bstring(stream, self.bytes) - def __eq__(self, other: Any) -> bool: + def __eq__(self, other: 'AString') -> bool: return isinstance(other, type(self)) and self.string == other.string def __repr__(self) -> str: return '[A]' + self._string - def __str__(self) -> str: - return self._string - -def read_astring(stream: IO[bytes]) -> str: +def read_astring(stream: io.BufferedIOBase) -> str: """ Read an ASCII string from the provided stream. - See `AString` for constraints on ASCII strings. + See AString for constraints on ASCII strings. - Args: - stream: Stream to read from. - - Returns: - Resulting string. - - Raises: - InvalidDataError + :param stream: Stream to read from. + :return: Resulting string. + :raises: InvalidDataError """ return AString.read(stream).string -def write_astring(stream: IO[bytes], string: str) -> int: +def write_astring(stream: io.BufferedIOBase, string: str) -> int: """ Write an ASCII string to a stream. See AString for constraints on ASCII strings. - Args: - stream: Stream to write to. - string: String to write. - - Returns: - Number of bytes written. - - Raises: - InvalidDataError + :param stream: Stream to write to. + :param string: String to write. + :return: Number of bytes written. + :raises: InvalidDataError """ return AString(string).write(stream) @@ -786,38 +643,37 @@ def write_astring(stream: IO[bytes], string: str) -> int: class ManhattanDelta: """ Class representing an axis-aligned ("Manhattan") vector. + + Has properties + .vertical (boolean, true if aligned along y-axis) + .value (int, signed length of the vector) """ - vertical: bool - """`True` if aligned along y-axis""" + vertical = None # type: bool + value = None # type: int - value: int - """signed length of the vector""" - - def __init__(self, x: int, y: int) -> None: + def __init__(self, x: int, y: int): """ - One of `x` or `y` _must_ be zero! + One of x or y _must_ be zero! - Args: - x: x-displacement - y: y-displacement + :param x: x-displacement + :param y: y-displacement """ x = int(x) y = int(y) if x != 0: if y != 0: - raise InvalidDataError(f'Non-Manhattan ManhattanDelta ({x}, {y})') + raise InvalidDataError('Non-Manhattan ManhattanDelta ({}, {})'.format(x, y)) self.vertical = False self.value = x else: self.vertical = True self.value = y - def as_list(self) -> list[int]: + def as_list(self) -> List[int]: """ Return a list representation of this vector. - Returns: - `[x, y]` + :return: [x, y] """ xy = [0, 0] xy[self.vertical] = self.value @@ -826,10 +682,9 @@ class ManhattanDelta: def as_uint(self) -> int: """ Return this vector encoded as an unsigned integer. - See `ManhattanDelta.from_uint()` for format details. + See ManhattanDelta.from_uint() for format details. - Returns: - uint encoding of this vector. + :return: uint encoding of this vector. """ return (encode_sint(self.value) << 1) | self.vertical @@ -841,87 +696,77 @@ class ManhattanDelta: The LSB of the encoded object is 1 if the vector is aligned to the y-axis, or 0 if aligned to the x-axis. The remaining bits are used to encode a signed integer containing - the signed length of the vector (see `encode_sint()` for format details). + the signed length of the vector (see encode_sint() for format details). - Args: - n: Unsigned integer representation of a `ManhattanDelta` vector. - - Returns: - The `ManhattanDelta` object that was encoded by `n`. + :param n: Unsigned integer representation of a ManhattanDelta vector. + :return: The ManhattanDelta object that was encoded by n. """ d = ManhattanDelta(0, 0) d.value = decode_sint(n >> 1) - d.vertical = bool(n & 0x01) + d.vertical = n & 0x01 return d @staticmethod - def read(stream: IO[bytes]) -> 'ManhattanDelta': + def read(stream: io.BufferedIOBase) -> 'ManhattanDelta': """ - Read a `ManhattanDelta` object from the provided stream. + Read a ManhattanDelta object from the provided stream. - See `ManhattanDelta.from_uint()` for format details. + See .from_uint() for format details. - Args: - stream: The stream to read from. - - Returns: - The `ManhattanDelta` object that was read from the stream. + :param stream: The stream to read from. + :return: The ManhattanDelta object that was read from the stream. """ n = read_uint(stream) return ManhattanDelta.from_uint(n) - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: """ - Write a `ManhattanDelta` object to the provided stream. + Write a ManhattanDelta object to the provided stream. - See `ManhattanDelta.from_uint()` for format details. + See .from_uint() for format details. - Args: - stream: The stream to write to. - - Returns: - The number of bytes written. + :param stream: The stream to write to. + :return: The number of bytes written. """ return write_uint(stream, self.as_uint()) - def __eq__(self, other: Any) -> bool: - return hasattr(other, 'as_list') and self.as_list() == other.as_list() + def __eq__(self, other: 'ManhattanDelta') -> bool: + return hasattr(other, as_list) and self.as_list() == other.as_list() def __repr__(self) -> str: - return str(self.as_list()) + return '{}'.format(self.as_list()) class OctangularDelta: """ Class representing an axis-aligned or 45-degree ("Octangular") vector. - """ - proj_mag: int - """projection of the vector onto the x or y axis (non-zero)""" - octangle: int - """ - bitfield: - bit 2: 1 if non-axis-aligned (non-Manhattan) - if Manhattan: - bit 1: 1 if direction is negative - bit 0: 1 if direction is y - if non-Manhattan: - bit 1: 1 if in lower half-plane - bit 0: 1 if x==-y + Has properties + .proj_mag (int, projection of the vector onto the x or y axis (non-zero)) + .octangle (int, bitfield: + bit 2: 1 if non-axis-aligned (non-Manhattan) + if Manhattan: + bit 1: 1 if direction is negative + bit 0: 1 if direction is y + if non-Manhattan: + bit 1: 1 if in lower half-plane + bit 0: 1 if x==-y - Resulting directions: - 0: +x, 1: +y, 2: -x, 3: -y, - 4: +x+y, 5: -x+y, - 6: +x-y, 7: -x-y + Resulting directions: + 0: +x, 1: +y, 2: -x, 3: -y, + 4: +x+y, 5: -x+y, + 6: +x-y, 7: -x-y + ) """ + proj_mag = None # type: int + octangle = None # type: int - def __init__(self, x: int, y: int) -> None: + def __init__(self, x: int, y: int): """ - Either `abs(x)==abs(y)`, `x==0`, or `y==0` _must_ be true! + Either abs(x)==abs(y), x==0, or y==0 _must_ be true! - Args: - x: x-displacement - y: y-displacement + :param x: x-displacement + :param y: y-displacement """ x = int(x) y = int(y) @@ -937,14 +782,13 @@ class OctangularDelta: self.proj_mag = abs(x) self.octangle = (1 << 2) | (yn << 1) | (xn != yn) else: - raise InvalidDataError(f'Non-octangular delta! ({x}, {y})') + raise InvalidDataError('Non-octangular delta! ({}, {})'.format(x, y)) - def as_list(self) -> list[int]: + def as_list(self) -> List[int]: """ Return a list representation of this vector. - Returns: - `[x, y]` + :return: [x, y] """ if self.octangle < 4: xy = [0, 0] @@ -952,7 +796,7 @@ class OctangularDelta: sign = self.octangle & 0x02 > 0 xy[axis] = self.proj_mag * (1 - 2 * sign) return xy - else: # noqa: RET505 + else: yn = (self.octangle & 0x02) > 0 xyn = (self.octangle & 0x01) > 0 ys = 1 - 2 * yn @@ -963,28 +807,24 @@ class OctangularDelta: def as_uint(self) -> int: """ Return this vector encoded as an unsigned integer. - See `OctangularDelta.from_uint()` for format details. + See OctangularDelta.from_uint() for format details. - Returns: - uint encoding of this vector. + :return: uint encoding of this vector. """ return (self.proj_mag << 3) | self.octangle @staticmethod def from_uint(n: int) -> 'OctangularDelta': """ - Construct an `OctangularDelta` object from its unsigned integer encoding. + Construct an OctangularDelta object from its unsigned integer encoding. - The low 3 bits are equal to `proj_mag`, as specified in the class + The low 3 bits are equal to .proj_mag, as specified in the class docstring. The remaining bits are used to encode an unsigned integer containing the length of the vector. - Args: - n: Unsigned integer representation of an `OctangularDelta` vector. - - Returns: - The `OctangularDelta` object that was encoded by `n`. + :param n: Unsigned integer representation of an OctangularDelta vector. + :return: The OctangularDelta object that was encoded by n. """ d = OctangularDelta(0, 0) d.proj_mag = n >> 3 @@ -992,90 +832,80 @@ class OctangularDelta: return d @staticmethod - def read(stream: IO[bytes]) -> 'OctangularDelta': + def read(stream: io.BufferedIOBase) -> 'OctangularDelta': """ - Read an `OctangularDelta` object from the provided stream. + Read an OctangularDelta object from the provided stream. - See `OctangularDelta.from_uint()` for format details. + See .from_uint() for format details. - Args: - stream: The stream to read from. - - Returns: - The `OctangularDelta` object that was read from the stream. + :param stream: The stream to read from. + :return: The OctangularDelta object that was read from the stream. """ n = read_uint(stream) return OctangularDelta.from_uint(n) - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: """ - Write an `OctangularDelta` object to the provided stream. + Write an OctangularDelta object to the provided stream. - See `OctangularDelta.from_uint()` for format details. + See .from_uint() for format details. - Args: - stream: The stream to write to. - - Returns: - The number of bytes written. + :param stream: The stream to write to. + :return: The number of bytes written. """ return write_uint(stream, self.as_uint()) - def __eq__(self, other: Any) -> bool: - return hasattr(other, 'as_list') and self.as_list() == other.as_list() + def __eq__(self, other: 'OctangularDelta') -> bool: + return hasattr(other, as_list) and self.as_list() == other.as_list() def __repr__(self) -> str: - return str(self.as_list()) + return '{}'.format(self.as_list()) class Delta: """ Class representing an arbitrary vector + + Has properties + .x (int) + .y (int) """ - x: int - """x-displacement""" + x = None # type: int + y = None # type: int - y: int - """y-displacement""" - - def __init__(self, x: int, y: int) -> None: + def __init__(self, x: int, y: int): """ - Args: - x: x-displacement - y: y-displacement + :param x: x-displacement + :param y: y-displacement """ x = int(x) y = int(y) self.x = x self.y = y - def as_list(self) -> list[int]: + def as_list(self) -> List[int]: """ Return a list representation of this vector. - Returns: - `[x, y]` + :return: [x, y] """ return [self.x, self.y] @staticmethod - def read(stream: IO[bytes]) -> 'Delta': + def read(stream: io.BufferedIOBase) -> 'Delta': """ - Read a `Delta` object from the provided stream. + Read a Delta object from the provided stream. The format consists of one or two unsigned integers. The LSB of the first integer is 1 if a second integer is present. If two integers are present, the remaining bits of the first - integer are an encoded signed integer (see `encode_sint()`), and + integer are an encoded signed integer (see encode_sint()), and the second integer is an encoded signed_integer. Otherwise, the remaining bits of the first integer are an encoded - `OctangularData` (see `OctangularData.from_uint()`). + OctangularData (see OctangularData.from_uint()). - Args: - stream: The stream to read from. - - Returns: - The `Delta` object that was read from the stream. + :param stream: The stream to read from. + :return: The Delta object that was read from the stream. """ n = read_uint(stream) if (n & 0x01) == 0: @@ -1085,64 +915,52 @@ class Delta: y = read_sint(stream) return Delta(x, y) - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: """ - Write a `Delta` object to the provided stream. + Write a Delta object to the provided stream. - See `Delta.from_uint()` for format details. + See .from_uint() for format details. - Args: - stream: The stream to write to. - - Returns: - The number of bytes written. + :param stream: The stream to write to. + :return: The number of bytes written. """ if self.x == 0 or self.y == 0 or abs(self.x) == abs(self.y): return write_uint(stream, OctangularDelta(self.x, self.y).as_uint() << 1) - size = write_uint(stream, (encode_sint(self.x) << 1) | 0x01) - size += write_uint(stream, encode_sint(self.y)) - return size + else: + size = write_uint(stream, (encode_sint(self.x) << 1) | 0x01) + size += write_uint(stream, encode_sint(self.y)) + return size - def __eq__(self, other: Any) -> bool: - return hasattr(other, 'as_list') and self.as_list() == other.as_list() + def __eq__(self, other: 'Delta') -> bool: + return hasattr(other, as_list) and self.as_list() == other.as_list() def __repr__(self) -> str: - return str(self.as_list()) + return '{}'.format(self.as_list()) -def read_repetition(stream: IO[bytes]) -> repetition_t: +def read_repetition(stream: io.BufferedIOBase) -> repetition_t: """ Read a repetition entry from the given stream. - Args: - stream: Stream to read from. - - Returns: - The repetition entry. - - Raises: - InvalidDataError: if an unexpected repetition type is read + :param stream: Stream to read from. + :return: The repetition entry. """ rtype = read_uint(stream) if rtype == 0: return ReuseRepetition.read(stream, rtype) - if rtype in (1, 2, 3, 8, 9): + elif rtype in (1, 2, 3, 8, 9): return GridRepetition.read(stream, rtype) - if rtype in (4, 5, 6, 7, 10, 11): + elif rtype in (4, 5, 6, 7, 10, 11): return ArbitraryRepetition.read(stream, rtype) - raise InvalidDataError(f'Unexpected repetition type: {rtype}') -def write_repetition(stream: IO[bytes], repetition: repetition_t) -> int: +def write_repetition(stream: io.BufferedIOBase, repetition: repetition_t) -> int: """ Write a repetition entry to the given stream. - Args: - stream: Stream to write to. - repetition: The repetition entry to write. - - Returns: - The number of bytes written. + :param stream: Stream to write to. + :param repetition: The repetition entry to write. + :return: The number of bytes written. """ return repetition.write(stream) @@ -1153,13 +971,13 @@ class ReuseRepetition: the most recently written repetition should be reused. """ @staticmethod - def read(_stream: IO[bytes], _repetition_type: int) -> 'ReuseRepetition': + def read(_stream: io.BufferedIOBase, _repetition_type: int) -> 'ReuseRepetition': return ReuseRepetition() - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: return write_uint(stream, 0) - def __eq__(self, other: Any) -> bool: + def __eq__(self, other: 'ReuseRepetition') -> bool: return isinstance(other, ReuseRepetition) def __repr__(self) -> str: @@ -1168,93 +986,80 @@ class ReuseRepetition: class GridRepetition: """ - A repetition entry denoting a 1D or 2D array of regularly-spaced elements. The - spacings are stored as one or two lattice vectors, and the extent of the grid - is stored as the number of elements along each lattice vector. + Class representing a repetition entry denoting a 1D or 2D array + of regularly-spaced elements. The spacings are stored as one or + two lattice vectors, and the extent of the grid is stored as the + number of elements along each lattice vector. + + This class has properties + .a_vector ([xa: int, ya: int], vector specifying a center-to-center + displacement between adjacent elements in the grid.) + .b_vector ([xb: int, yb: int] or None, a second displacement, present if + a 2D grid is being specified.) + .a_count (int >= 1, number of elements along the grid axis specified by + .a_vector) + .b_count (int >= 1 or None, number of elements along the grid axis + specified by .b_vector) """ + a_vector = None # type: List[int] + b_vector = None # type: List[int] or None + a_count = None # type: int + b_count = None # type: int or None - a_vector: list[int] - """`(xa, ya)` vector specifying a center-to-center - displacement between adjacent elements in the grid. - """ - - b_vector: list[int] | None = None - """`(xb, yb)`, a second displacement, - present if a 2D grid is being specified. - """ - - a_count: int - """number of elements (>=1) along the grid axis specified by `a_vector`.""" - - b_count: int | None = None - """Number of elements (>=1) along the grid axis - specified by `b_vector`, if `b_vector` is not `None`. - """ - - def __init__( - self, - a_vector: Sequence[int], - a_count: int, - b_vector: Sequence[int] | None = None, - b_count: int | None = None, - ) -> None: + def __init__(self, + a_vector: List[int], + a_count: int, + b_vector: List[int] = None, + b_count: int = None): """ - Args: - a_vector: First lattice vector, of the form `[x, y]`. - Specifies center-to-center spacing between adjacent elements. - a_count: Number of elements in the a_vector direction. - b_vector: Second lattice vector, of the form `[x, y]`. - Specifies center-to-center spacing between adjacent elements. - Can be omitted when specifying a 1D array. - b_count: Number of elements in the `b_vector` direction. - Should be omitted if `b_vector` was omitted. - - Raises: - InvalidDataError: if `b_count` and `b_vector` inputs conflict - with each other or if `a_count < 1`. + :param a_vector: First lattice vector, of the form [x, y]. + Specifies center-to-center spacing between adjacent elements. + :param a_count: Number of elements in the a_vector direction. + :param b_vector: Second lattice vector, of the form [x, y]. + Specifies center-to-center spacing between adjacent elements. + Can be omitted when specifying a 1D array. + :param b_count: Number of elements in the b_vector direction. + Should be omitted if b_vector was omitted. + :raises: InvalidDataError if b_* inputs conflict with each other + or a_count < 1. """ - a_count = int(a_count) - if b_count is not None: - b_count = int(b_count) + self.a_vector = a_vector + self.b_vector = b_vector + self.a_count = a_count + self.b_count = b_count - if b_vector is None or b_count is None: - if b_vector is not None or b_count is not None: + if self.b_vector is None or self.b_count is None: + if self.b_vector is not None or self.b_count is not None: raise InvalidDataError('Repetition has only one of' 'b_vector and b_count') else: - if b_count < 1: + if self.b_count < 1: raise InvalidDataError('Repetition has too-small b_count') - if b_count < 2: - b_count = None - b_vector = None - warnings.warn('Removed b_count and b_vector since b_count == 1', stacklevel=2) + if self.b_count < 2: + self.b_count = None + self.b_vector = None + print('Warning: removed b_count and b_vector since b_count == 1') + # TODO: warn here - if a_count < 2: - raise InvalidDataError(f'Repetition has too-small a_count: {a_count}') - - self.a_vector = list(a_vector) - self.b_vector = list(b_vector) if b_vector is not None else None + if self.a_count < 2: + raise InvalidDataError('Repetition has too-small x-count: ' + '{}'.format(a_count)) + self.a_vector = a_vector + self.b_vector = b_vector self.a_count = a_count self.b_count = b_count @staticmethod - def read(stream: IO[bytes], repetition_type: int) -> 'GridRepetition': + def read(stream: io.BufferedIOBase, repetition_type: int) -> 'GridRepetition': """ - Read a `GridRepetition` from a stream. + Read a GridRepetition from a stream. - Args: - stream: Stream to read from. - repetition_type: Repetition type as defined in OASIS repetition spec. - Valid types are 1, 2, 3, 8, 9. - - Returns: - `GridRepetition` object read from stream. - - Raises: - InvalidDataError: if `repetition_type` is invalid. + :param stream: Stream to read from. + :param repetition_type: Repetition type as defined in OASIS repetition spec. + Valid types are 1, 2, 3, 8, 9. + :return: GridRepetition object read from stream. + :raises InvalidDataError if repetition_type is invalid. """ - nb: int | None - b_vector: list[int] | None if repetition_type == 1: na = read_uint(stream) + 2 nb = read_uint(stream) + 2 @@ -1281,28 +1086,24 @@ class GridRepetition: a_vector = Delta.read(stream).as_list() b_vector = None else: - raise InvalidDataError(f'Invalid type for grid repetition {repetition_type}') + raise InvalidDataError('Invalid type for grid repetition ' + '{}'.format(repetition_type)) return GridRepetition(a_vector, na, b_vector, nb) - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: """ - Write the `GridRepetition` to a stream. + Write the GridRepetition to a stream. - A minimal representation is written (e.g., if `b_count==1`, + A minimal representation is written (e.g., if b_count==1, a 1D grid is written) - Args: - stream: Stream to write to. - - Returns: - Number of bytes written. - - Raises: - InvalidDataError: if repetition is malformed. + :param stream: Stream to write to. + :return: Number of bytes written. + :raises: InvalidDataError if repetition is malformed. """ if self.b_vector is None or self.b_count is None: if self.b_vector is not None or self.b_count is not None: - raise InvalidDataError(f'Malformed repetition {self}') + raise InvalidDataError('Malformed repetition {}'.format(self)) if self.a_vector[1] == 0: size = write_uint(stream, 2) @@ -1316,7 +1117,7 @@ class GridRepetition: size = write_uint(stream, 9) size += write_uint(stream, self.a_count - 2) size += Delta(*self.a_vector).write(stream) - else: # noqa: PLR5501 + else: if self.a_vector[1] == 0 and self.b_vector[0] == 0: size = write_uint(stream, 1) size += write_uint(stream, self.a_count - 2) @@ -1337,65 +1138,51 @@ class GridRepetition: size += Delta(*self.b_vector).write(stream) return size - def __eq__(self, other: Any) -> bool: - if not isinstance(other, type(self)): - return False - if self.a_count != other.a_count or self.b_count != other.b_count: - return False - if any(self.a_vector[ii] != other.a_vector[ii] for ii in range(2)): - return False - if self.b_vector is None and other.b_vector is None: - return True - if self.b_vector is None or other.b_vector is None: - return False - if any(self.b_vector[ii] != other.b_vector[ii] for ii in range(2)): # noqa: SIM103 - return False - return True + def __eq__(self, other: 'GridRepetition') -> bool: + return isinstance(other, type(self)) and \ + self.a_count == other.a_count and \ + self.b_count == other.b_count and \ + self.a_vector == other.a_vector and \ + self.b_vector == other.b_vector def __repr__(self) -> str: - return f'GridRepetition: ({self.a_count} : {self.a_vector} | {self.b_count} : {self.b_vector})' + return 'GridRepetition: ({} : {} | {} : {})'.format(self.a_count, self.a_vector, + self.b_count, self.b_vector) class ArbitraryRepetition: """ Class representing a repetition entry denoting a 1D or 2D array of arbitrarily-spaced elements. + + Properties: + .x_displacements (List[int], x-displacements between elements) + .y_displacements (List[int], y-displacements between elements) """ - x_displacements: list[int] - """x-displacements between consecutive elements""" + x_displacements = None # type: List[int] + y_displacements = None # type: List[int] - y_displacements: list[int] - """y-displacements between consecutive elements""" - - def __init__( - self, - x_displacements: Sequence[int], - y_displacements: Sequence[int], - ) -> None: + def __init__(self, + x_displacements: List[int], + y_displacements: List[int]): """ - Args: - x_displacements: x-displacements between consecutive elements - y_displacements: y-displacements between consecutive elements + :param x_displacements: x-displacements between consecutive elements + :param y_displacements: y-displacements between consecutive elements """ - self.x_displacements = list(x_displacements) - self.y_displacements = list(y_displacements) + self.x_displacements = x_displacements + self.y_displacements = y_displacements @staticmethod - def read(stream: IO[bytes], repetition_type: int) -> 'ArbitraryRepetition': + def read(stream: io.BufferedIOBase, repetition_type: int) -> 'ArbitraryRepetition': """ - Read an `ArbitraryRepetition` from a stream. + Read an ArbitraryRepetition from a stream. - Args: - stream: Stream to read from. - repetition_type: Repetition type as defined in OASIS repetition spec. - Valid types are 4, 5, 6, 7, 10, 11. - - Returns: - `ArbitraryRepetition` object read from stream. - - Raises: - InvalidDataError: if `repetition_type` is invalid. + :param stream: Stream to read from. + :param repetition_type: Repetition type as defined in OASIS repetition spec. + Valid types are 4, 5, 6, 7, 10, 11. + :return: ArbitraryRepetition object read from stream. + :raises InvalidDataError if repetition_type is invalid. """ if repetition_type == 4: n = read_uint(stream) + 1 @@ -1433,24 +1220,21 @@ class ArbitraryRepetition: x_displacements.append(x * mult) y_displacements.append(y * mult) else: - raise InvalidDataError(f'Invalid ArbitraryRepetition repetition_type: {repetition_type}') + raise InvalidDataError('Invalid ArbitraryRepetition repetition_type: {}'.format(repetition_type)) return ArbitraryRepetition(x_displacements, y_displacements) - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: """ - Write the `ArbitraryRepetition` to a stream. + Write the ArbitraryRepetition to a stream. A minimal representation is attempted; common factors in the displacements will be factored out, and lists of zeroes will be omitted. - Args: - stream: Stream to write to. - - Returns: - Number of bytes written. + :param stream: Stream to write to. + :return: Number of bytes written. """ - def get_gcd(vals: list[int]) -> int: + def get_gcd(vals: List[int]) -> int: """ Get the greatest common denominator of a list of ints. """ @@ -1492,62 +1276,64 @@ class ArbitraryRepetition: size = write_uint(stream, 10) size += write_uint(stream, len(self.x_displacements) - 1) size += sum(Delta(x, y).write(stream) - for x, y in zip(self.x_displacements, self.y_displacements, strict=True)) + for x, y in zip(self.x_displacements, self.y_displacements)) else: size = write_uint(stream, 11) size += write_uint(stream, len(self.x_displacements) - 1) size += write_uint(stream, gcd) size += sum(Delta(x // gcd, y // gcd).write(stream) - for x, y in zip(self.x_displacements, self.y_displacements, strict=True)) + for x, y in zip(self.x_displacements, self.y_displacements)) return size - def __eq__(self, other: Any) -> bool: - return (isinstance(other, type(self)) - and self.x_displacements == other.x_displacements - and self.y_displacements == other.y_displacements) + + def __eq__(self, other: 'ArbitraryRepetition') -> bool: + return isinstance(other, type(self)) and self.x_displacements == other.x_displacements and self.y_displacements == other.y_displacements def __repr__(self) -> str: - return f'ArbitraryRepetition: x{self.x_displacements} y{self.y_displacements})' + return 'ArbitraryRepetition: x{} y{})'.format(self.x_displacements, self.y_displacements) -def read_point_list( - stream: IO[bytes], - implicit_closed: bool, - ) -> Sequence[Sequence[int]]: +def read_point_list(stream: io.BufferedIOBase) -> List[List[int]]: """ Read a point list from a stream. - Args: - stream: Stream to read from. - implicit_closed: If true, the source point list is assumed to be implicitly - closed, and will be explicitly closed in the return value. - - Returns: - Point list of the form `[[x0, y0], [x1, y1], ...]` - - Raises: - InvalidDataError: if an invalid list type is read. + :param stream: Stream to read from. + :return: Point list of the form [[x0, y0], [x1, y1], ...] """ list_type = read_uint(stream) list_len = read_uint(stream) if list_type == 0: points = [] + dx, dy = 0, 0 for i in range(list_len): + point = [0, 0] n = read_sint(stream) if n == 0: raise InvalidDataError('Zero-sized 1-delta') - point = [0, 0] point[i % 2] = n points.append(point) + if i % 2: + dy += n + else: + dx += n + points.append([-dx, 0]) + points.append([0, -dy]) elif list_type == 1: points = [] + dx, dy = 0, 0 for i in range(list_len): + point = [0, 0] n = read_sint(stream) if n == 0: - raise InvalidDataError('Zero-sized 1-delta') - point = [0, 0] + raise Exception('Zero-sized 1-delta') point[(i + 1) % 2] = n points.append(point) + if i % 2: + dx += n + else: + dy += n + points.append([0, -dy]) + points.append([-dx, 0]) elif list_type == 2: points = [ManhattanDelta.read(stream).as_list() for _ in range(list_len)] elif list_type == 3: @@ -1557,75 +1343,45 @@ def read_point_list( elif list_type == 5: deltas = [Delta.read(stream).as_list() for _ in range(list_len)] if _USE_NUMPY: - points = numpy.cumsum(deltas, axis=0) + delta_x, delta_y = zip(*deltas) + x = numpy.cumsum(delta_x) + y = numpy.cumsum(delta_y) + points = list(zip(x, y)) else: points = [] x = 0 y = 0 - for delta in deltas: - x += delta[0] - y += delta[1] + for _ in range(list_len): + delta = Delta.read(stream) + x += delta.x + y += delta.y points.append([x, y]) else: - raise InvalidDataError('Invalid point list type') - - if not implicit_closed: - return points - - if _USE_NUMPY: - dx, dy = numpy.sum(points, axis=0) - else: - dx = sum(x for x, _y in points) - dy = sum(y for _x, y in points) - - if list_type == 0: - close_points = [[-dx, 0], [0, -dy]] - elif list_type == 1: - close_points = [[0, -dy], [-dx, 0]] - elif list_type == 2: - assert (dx == 0) or (dy == 0) - close_points = [[-dx, -dy]] - elif list_type == 3: - assert 0 in (dx, dy) or dx in (dy, -dy) - close_points = [[-dx, -dy]] - else: - close_points = [[-dx, -dy]] - - if _USE_NUMPY: - points = numpy.vstack((points, close_points)) - else: - points += close_points - + raise Exception('Invalid point list type') return points -def write_point_list( - stream: IO[bytes], - points: 'list[Sequence[int]] | NDArray', - fast: bool = False, - implicit_closed: bool = True - ) -> int: +def write_point_list(stream: io.BufferedIOBase, + points: List[List[int]], + fast: bool = False, + implicit_closed: bool = True + ) -> int: """ Write a point list to a stream. - Args: - stream: Stream to write to. - points: List of points, of the form `[[x0, y0], [x1, y1], ...]` - fast: If `True`, avoid searching for a compact representation for - the point list. - implicit_closed: Set to True if the list represents an implicitly - closed polygon, i.e. there is an implied line segment from `points[-1]` - to `points[0]`. If False, such segments are ignored, which can result in - a more compact representation for non-closed paths (e.g. a Manhattan - path with non-colinear endpoints). If unsure, use the default. - Default `True`. - - Returns: - Number of bytes written. + :param stream: Stream to write to. + :param points: List of points, of the form [[x0, y0], [x1, y1], ...] + :param fast: If True, avoid searching for a compact representation for + the point list. + :param implicit_closed: Set to True if the list represents an implicitly + closed polygon, i.e. there is an implied line segment from points[-1] + to points[0]. If False, such segments are ignored, which can result in a + more compact representation for non-closed paths (e.g. a Manhattan + path with non-colinear endpoints). If unsure, use the default. + Default True. + :return: Number of bytes written. """ # If we're in a hurry, just write the points as arbitrary Deltas - if _USE_NUMPY: - points = numpy.asarray(points, dtype=int) if fast: size = write_uint(stream, 4) size += write_uint(stream, len(points)) @@ -1644,10 +1400,11 @@ def write_point_list( h_first = False v_first = False break - elif point[1] != previous[1] or point[0] == previous[0]: - h_first = False - v_first = False - break + else: + if point[1] != previous[1] or point[0] == previous[0]: + h_first = False + v_first = False + break previous = point # If one of h_first or v_first, write a bunch of 1-deltas @@ -1656,27 +1413,26 @@ def write_point_list( size += write_uint(stream, len(points)) size += sum(write_sint(stream, x + y) for x, y in points) return size - if v_first: + elif v_first: size = write_uint(stream, 1) size += write_uint(stream, len(points)) size += sum(write_sint(stream, x + y) for x, y in points) return size # Try writing a bunch of Manhattan or Octangular deltas - deltas: list[ManhattanDelta] | list[OctangularDelta] | list[Delta] list_type = None try: deltas = [ManhattanDelta(x, y) for x, y in points] if implicit_closed: ManhattanDelta(points[-1][0] - points[0][0], points[-1][1] - points[0][1]) list_type = 2 - except InvalidDataError: + except: try: deltas = [OctangularDelta(x, y) for x, y in points] if implicit_closed: OctangularDelta(points[-1][0] - points[0][0], points[-1][1] - points[0][1]) list_type = 3 - except InvalidDataError: + except: pass if list_type is not None: size = write_uint(stream, list_type) @@ -1702,19 +1458,18 @@ def write_point_list( deltas = [Delta(*points[0])] + [Delta(x, y) for x, y in diff] else: previous = [0, 0] - diffl = [] + diff = [] for point in points: - d = [point[0] - previous[0], - point[1] - previous[1]] + d = [point[0] - previous[0], point[1] - previous[1]] previous = point - diffl.append(d) + diff.append(d) - if sum(sum(p) for p in points) < sum(sum(d) for d in diffl) * decision_factor: + if sum(sum(p) for p in points) < sum(sum(d) for d in diff) * decision_factor: list_type = 4 deltas = [Delta(x, y) for x, y in points] else: list_type = 5 - deltas = [Delta(x, y) for x, y in diffl] + deltas = [Delta(x, y) for x, y in diff] size = write_uint(stream, list_type) size += write_uint(stream, len(points)) @@ -1725,30 +1480,30 @@ def write_point_list( class PropStringReference: """ Reference to a property string. + + Properties: + .ref (int, ID of the target) + .ref_type (Type, Type of the target: bytes, NString, or AString) """ - ref: int - """ID of the target""" + ref = None # type: int + reference_type = None # type: Type - reference_type: type - """Type of the target: `bytes`, `NString`, or `AString`""" - - def __init__(self, ref: int, ref_type: type) -> None: + def __init__(self, ref: int, ref_type: Type): """ - Args: - ref: ID number of the target. - ref_type: Type of the target. One of bytes, NString, AString. + :param ref: ID number of the target. + :param ref_type: Type of the target. One of bytes, NString, AString. """ self.ref = ref self.ref_type = ref_type - def __eq__(self, other: Any) -> bool: - return isinstance(other, type(self)) and self.ref == other.ref and self.reference_type is other.reference_type + def __eq__(self, other: 'PropStringReference') -> bool: + return isinstance(other, type(self)) and self.ref == other.ref and self.reference_type == other.reference_type def __repr__(self) -> str: - return f'[{self.ref_type} : {self.ref}]' + return '[{} : {}]'.format(self.ref_type, self.ref) -def read_property_value(stream: IO[bytes]) -> property_value_t: +def read_property_value(stream: io.BufferedIOBase) -> property_value_t: """ Read a property value from a stream. @@ -1759,76 +1514,67 @@ def read_property_value(stream: IO[bytes]) -> property_value_t: 0...7: real number; property value type is reused for real number type 8: unsigned integer 9: signed integer - 10: ASCII string (`AString`) - 11: binary string (`bytes`) - 12: name string (`NString`) - 13: `PropStringReference` to `AString` - 14: `PropStringReference` to `bstring` (i.e., to `bytes`) - 15: `PropStringReference` to `NString` + 10: ASCII string (AString) + 11: binary string (bytes) + 12: name string (NString) + 13: PropstringReference to AString + 14: PropstringReference to bstring (i.e., to bytes) + 15: PropstringReference to NString - Args: - stream: Stream to read from. - - Returns: - Value of the property, depending on type. - - Raises: - InvalidDataError: if an invalid type is read. + :param stream: Stream to read from. + :return: Value of the property, depending on type. + :raises: InvalidDataError if an invalid type is read. """ - ref_type: type prop_type = read_uint(stream) if 0 <= prop_type <= 7: return read_real(stream, prop_type) - if prop_type == 8: + elif prop_type == 8: return read_uint(stream) - if prop_type == 9: + elif prop_type == 9: return read_sint(stream) - if prop_type == 10: + elif prop_type == 10: return AString.read(stream) - if prop_type == 11: + elif prop_type == 11: return read_bstring(stream) - if prop_type == 12: + elif prop_type == 12: return NString.read(stream) - if prop_type == 13: + elif prop_type == 13: ref_type = AString ref = read_uint(stream) return PropStringReference(ref, ref_type) - if prop_type == 14: + elif prop_type == 14: ref_type = bytes ref = read_uint(stream) return PropStringReference(ref, ref_type) - if prop_type == 15: + elif prop_type == 15: ref_type = NString ref = read_uint(stream) return PropStringReference(ref, ref_type) - raise InvalidDataError(f'Invalid property type: {prop_type}') + else: + raise InvalidDataError('Invalid property type: {}'.format(prop_type)) -def write_property_value( - stream: IO[bytes], - value: property_value_t, - force_real: bool = False, - force_signed_int: bool = False, - force_float32: bool = False, - ) -> int: +def write_property_value(stream: io.BufferedIOBase, + value: property_value_t, + force_real: bool = False, + force_signed_int: bool = False, + force_float32: bool = False + ) -> int: """ Write a property value to a stream. - See `read_property_value()` for format details. + See read_property_value() for format details. - Args: - stream: Stream to write to. - value: Property value to write. Can be an integer, a real number, - `bytes` (`bstring`), `NString`, `AString`, or a `PropstringReference`. - force_real: If `True` and value is an integer, writes an integer- - valued real number instead of a plain integer. Default `False`. - force_signed_int: If `True` and value is a positive integer, - writes a signed integer. Default `False`. - force_float32: If `True` and value is a float, writes a 32-bit - float (real number) instead of a 64-bit float. - - Returns: - Number of bytes written. + :param stream: Stream to write to. + :param value: Property value to write. Can be an integer, a real number, + bytes (bstring), NString, AString, or a PropstringReference. + :param force_real: If True and value is an integer, writes an integer- + valued real number instead of a plain integer. Default False. + :param force_signed_int: If True and value is a positive integer, + writes a signed integer. Default false. + :param force_float32: If True and value is a float, writes a 32-bit + float (real number) instead of a 64-bit float. + :return: Number of bytes written. """ if isinstance(value, int) and not force_real: if force_signed_int or value < 0: @@ -1837,7 +1583,7 @@ def write_property_value( else: size = write_uint(stream, 8) size += write_uint(stream, value) - elif isinstance(value, Fraction | float | int): + elif isinstance(value, real_t): size = write_real(stream, value, force_float32) elif isinstance(value, AString): size = write_uint(stream, 10) @@ -1849,19 +1595,19 @@ def write_property_value( size = write_uint(stream, 12) size += value.write(stream) elif isinstance(value, PropStringReference): - if value.ref_type is AString: + if value.ref_type == AString: size = write_uint(stream, 13) - elif value.ref_type is bytes: + elif value.ref_type == bytes: size = write_uint(stream, 14) - if value.ref_type is AString: + if value.ref_type == AString: size = write_uint(stream, 15) size += write_uint(stream, value.ref) else: - raise InvalidDataError(f'Invalid property type: {type(value)} ({value})') + raise Exception('Invalid property type: {} ({})'.format(type(value), value)) return size -def read_interval(stream: IO[bytes]) -> tuple[int | None, int | None]: +def read_interval(stream: io.BufferedIOBase) -> Tuple[int or None]: """ Read an interval from a stream. These are used for storing layer info. @@ -1874,127 +1620,112 @@ def read_interval(stream: IO[bytes]) -> tuple[int | None, int | None]: type 3: a, a (unsigned integer a) type 4: a, b (unsigned integers a, b) - Args: - stream: Stream to read from. - - Returns: - `(lower, upper)`, where - `lower` can be `None` if there is an implicit lower bound of `0` - `upper` can be `None` if there is no upper bound (`inf`) - - Raises: - InvalidDataError: On malformed data. + :param stream: Stream to read from. + :return: (lower, upper), where + lower can be None if there is an implicit lower bound of 0 + upper can be None if there is no upper bound (inf) """ interval_type = read_uint(stream) if interval_type == 0: return None, None - if interval_type == 1: + elif interval_type == 1: return None, read_uint(stream) - if interval_type == 2: + elif interval_type == 2: return read_uint(stream), None - if interval_type == 3: + elif interval_type == 3: v = read_uint(stream) return v, v - if interval_type == 4: + elif interval_type == 4: return read_uint(stream), read_uint(stream) - raise InvalidDataError(f'Unrecognized interval type: {interval_type}') -def write_interval( - stream: IO[bytes], - min_bound: int | None = None, - max_bound: int | None = None, - ) -> int: +def write_interval(stream: io.BufferedIOBase, + min_bound: int or None = None, + max_bound: int or None = None + ) -> int: """ Write an interval to a stream. - Used for layer data; see `read_interval()` for format details. + Used for layer data; see read_interval for format details. - Args: - stream: Stream to write to. - min_bound: Lower bound on the interval, can be None (implicit 0, default) - max_bound: Upper bound on the interval, can be None (unbounded, default) - - Returns: - Number of bytes written. + :param stream: Stream to write to. + :param min_bound: Lower bound on the interval, can be None (implicit 0, default) + :param max_bound: Upper bound on the interval, can be None (unbounded, default) + :return: Number of bytes written. """ if min_bound is None: if max_bound is None: return write_uint(stream, 0) - return write_uint(stream, 1) + write_uint(stream, max_bound) - if max_bound is None: - return write_uint(stream, 2) + write_uint(stream, min_bound) - if min_bound == max_bound: - return write_uint(stream, 3) + write_uint(stream, min_bound) - size = write_uint(stream, 4) - size += write_uint(stream, min_bound) - size += write_uint(stream, max_bound) - return size + else: + return write_uint(stream, 1) + write_uint(stream, max_bound) + else: + if max_bound is None: + return write_uint(stream, 2) + write_uint(stream, min_bound) + else: + size = write_uint(stream, 3) + size += write_uint(stream, min_bound) + size += write_uint(stream, max_bound) + return size class OffsetEntry: """ Entry for the file's offset table. - """ - strict: bool = False - """ - If `False`, the records pointed to by this offset entry may also appear - elsewhere in the file. If `True`, all records of the type pointed to by - this offset entry must be present in a contiuous block at the specified - offset [pad records also allowed]. - Additionally: - - All references to strict-mode records must be explicit (using - `reference_number`). - - The offset may point to an encapsulating CBlock record, if the first - record in that CBlock is of the target record type. A strict mode - table cannot begin in the middle of a CBlock. + Properties: + .strict (bool, If False, the records pointed to by this + offset entry may also appear elsewhere in the file. + If True, all records of the type pointed to by this + offset entry must be present in a contiuous block at + the specified offset [pad records also allowed]. + Additionally: + All references to strict-mode records must be + explicit (using reference_number). + The offset may point to an encapsulating CBlock + record, if the first record in that CBlock is + of the target record type. A strict mode table + cannot begin in the middle of a CBlock. + ) + .offset (int, offset from the start of the file; may be 0 + for records that are not present.) """ + strict = False # type: bool + offset = 0 # type: int - offset: int = 0 - """offset from the start of the file; 0 for records that are not present.""" - - def __init__(self, strict: bool = False, offset: int = 0) -> None: + def __init__(self, strict: bool = False, offset: int = 0): """ - Args: - strict: `True` if the records referenced are written in - strict mode (see class docstring). Default `False`. - offset: Offset from the start of the file for the - referenced records; may be `0` if records are absent. - Default `0`. + :param strict: True if the records referenced are written in + strict mode (see class docstring). Default False. + :param offset: Offset from the start of the file for the + referenced records; may be 0 if records are absent. + Default 0. """ self.strict = strict self.offset = offset @staticmethod - def read(stream: IO[bytes]) -> 'OffsetEntry': + def read(stream: io.BufferedIOBase) -> 'OffsetEntry': """ Read an offset entry from a stream. - Args: - stream: Stream to read from. - - Returns: - Offset entry that was read. + :param stream: Stream to read from. + :return: Offset entry that was read. """ entry = OffsetEntry() entry.strict = read_uint(stream) > 0 entry.offset = read_uint(stream) return entry - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: """ Write this offset entry to a stream. - Args: - stream: Stream to write to. - - Returns: - Number of bytes written + :param stream: Stream to write to. + :return: Number of bytes written """ return write_uint(stream, self.strict) + write_uint(stream, self.offset) def __repr__(self) -> str: - return f'Offset(s: {self.strict}, o: {self.offset})' + return 'Offset(s: {}, o: {})'.format(self.strict, self.offset) class OffsetTable: @@ -2010,33 +1741,38 @@ class OffsetTable: XName which are stored in the above order in the file's offset table. + + Proerties: + .cellnames (OffsetEntry) + .textstrings (OffsetEntry) + .propnames (OffsetEntry) + .propstrings (OffsetEntry) + .layernames (OffsetEntry) + .xnames (OffsetEntry) """ - cellnames: OffsetEntry - textstrings: OffsetEntry - propnames: OffsetEntry - propstrings: OffsetEntry - layernames: OffsetEntry - xnames: OffsetEntry + cellnames = None # type: OffsetEntry + textstrings= None # type: OffsetEntry + propnames = None # type: OffsetEntry + propstrings = None # type: OffsetEntry + layernames = None # type: OffsetEntry + xnames = None # type: OffsetEntry - def __init__( - self, - cellnames: OffsetEntry | None = None, - textstrings: OffsetEntry | None = None, - propnames: OffsetEntry | None = None, - propstrings: OffsetEntry | None = None, - layernames: OffsetEntry | None = None, - xnames: OffsetEntry | None = None, - ) -> None: + def __init__(self, + cellnames: OffsetEntry = None, + textstrings: OffsetEntry = None, + propnames: OffsetEntry = None, + propstrings: OffsetEntry = None, + layernames: OffsetEntry = None, + xnames: OffsetEntry = None): """ - All parameters default to a non-strict entry with offset `0`. + All parameters default to a non-strict entry with offset 0. - Args: - cellnames: `OffsetEntry` for `CellName` records. - textstrings: `OffsetEntry` for `TextString` records. - propnames: `OffsetEntry` for `PropName` records. - propstrings: `OffsetEntry` for `PropString` records. - layernames: `OffsetEntry` for `LayerName` records. - xnames: `OffsetEntry` for `XName` records. + :param cellnames: OffsetEntry for CellName records. + :param textstrings: OffsetEntry for TextString records. + :param propnames: OffsetEntry for PropName records. + :param propstrings: OffsetEntry for PropString records. + :param layernames: OffsetEntry for LayerNamerecords. + :param xnames: OffsetEntry for XName records. """ if cellnames is None: cellnames = OffsetEntry() @@ -2044,7 +1780,7 @@ class OffsetTable: textstrings = OffsetEntry() if propnames is None: propnames = OffsetEntry() - if propstrings is None: + if propstrings is None: propstrings = OffsetEntry() if layernames is None: layernames = OffsetEntry() @@ -2059,16 +1795,13 @@ class OffsetTable: self.xnames = xnames @staticmethod - def read(stream: IO[bytes]) -> 'OffsetTable': + def read(stream: io.BufferedIOBase) -> 'OffsetTable': """ Read an offset table from a stream. See class docstring for format details. - Args: - stream: Stream to read from. - - Returns: - The offset table that was read. + :param stream: Stream to read from. + :return: The offset table that was read. """ table = OffsetTable() table.cellnames = OffsetEntry.read(stream) @@ -2079,16 +1812,13 @@ class OffsetTable: table.xnames = OffsetEntry.read(stream) return table - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: """ Write this offset table to a stream. See class docstring for format details. - Args: - stream: Stream to write to. - - Returns: - Number of bytes written. + :param stream: Stream to write to. + :return: Number of bytes written. """ size = self.cellnames.write(stream) size += self.textstrings.write(stream) @@ -2103,36 +1833,28 @@ class OffsetTable: self.propstrings, self.layernames, self.xnames]) -def read_u32(stream: IO[bytes]) -> int: +def read_u32(stream: io.BufferedIOBase) -> int: """ Read a 32-bit unsigned integer (little endian) from a stream. - Args: - stream: Stream to read from. - - Returns: - The integer that was read. + :param stream: Stream to read from. + :return: The integer that was read. """ b = _read(stream, 4) - return struct.unpack(' int: +def write_u32(stream: io.BufferedIOBase, n: int) -> int: """ Write a 32-bit unsigned integer (little endian) to a stream. - Args: - stream: Stream to write to. - n: Integer to write. - - Returns: - The number of bytes written (4). - - Raises: - SignedError: if `n` is negative. + :param stream: Stream to write to. + :param n: Integer to write. + :return: The number of bytes written (4). + :raises: SignedError if n is negative. """ if n < 0: - raise SignedError(f'Negative u32: {n}') + raise SignedError('Negative u32: {}'.format(n)) return stream.write(struct.pack(' None: + def __init__(self, checksum_type: int, checksum: int = None): """ - Args: - checksum_type: 0,1,2 (No checksum, crc32, checksum32) - checksum: Value of the checksum, or None. - - Raises: - InvalidDataError: if `checksum_type` is invalid, or - unexpected `checksum` is present. + :param checksum_type: 0,1,2 (No checksum, crc32, checksum32) + :param checksum: Value of the checksum, or None. + :raises: InvalidDataError if checksum_type is invalid, or + unexpected checksum is present. """ if checksum_type < 0 or checksum_type > 2: raise InvalidDataError('Invalid validation type') @@ -2174,81 +1890,65 @@ class Validation: self.checksum = checksum @staticmethod - def read(stream: IO[bytes]) -> 'Validation': + def read(stream: io.BufferedIOBase) -> 'Validation': """ Read a validation entry from a stream. See class docstring for format details. - Args: - stream: Stream to read from. - - Returns: - The validation entry that was read. - - Raises: - InvalidDataError: if an invalid validation type was encountered. + :param stream: Stream to read from. + :return: The validation entry that was read. + :raises: InvalidDataError if an invalid validation type was encountered. """ checksum_type = read_uint(stream) if checksum_type == 0: checksum = None - elif checksum_type in (1, 2): + elif checksum_type == 1: + checksum = read_u32(stream) + elif checksum_type == 2: checksum = read_u32(stream) else: raise InvalidDataError('Invalid validation type!') return Validation(checksum_type, checksum) - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: """ Write this validation entry to a stream. See class docstring for format details. - Args: - stream: Stream to write to. - - Returns: - Number of bytes written. - - Raises: - InvalidDataError: if the checksum type can't be handled. + :param stream: Stream to write to. + :return: Number of bytes written. """ if self.checksum_type == 0: return write_uint(stream, 0) - if self.checksum is None: - raise InvalidDataError(f'Checksum is empty but type is {self.checksum_type}') - if self.checksum_type == 1: + elif self.checksum_type == 1: return write_uint(stream, 1) + write_u32(stream, self.checksum) - if self.checksum_type == 2: + elif self.checksum_type == 2: return write_uint(stream, 2) + write_u32(stream, self.checksum) - raise InvalidDataError(f'Unrecognized checksum type: {self.checksum_type}') def __repr__(self) -> str: - return f'Validation(type: {self.checksum_type} sum: {self.checksum})' + return 'Validation(type: {} sum: {})'.format(self.checksum_type, self.checksum) -def write_magic_bytes(stream: IO[bytes]) -> int: +def write_magic_bytes(stream: io.BufferedIOBase) -> int: """ Write the magic byte sequence to a stream. - Args: - stream: Stream to write to. - - Returns: - Number of bytes written. + :param stream: Stream to write to. + :return: Number of bytes written. """ return stream.write(MAGIC_BYTES) -def read_magic_bytes(stream: IO[bytes]) -> None: +def read_magic_bytes(stream: io.BufferedIOBase): """ Read the magic byte sequence from a stream. - Raise an `InvalidDataError` if it was not found. + Raise an InvalidDataError if it was not found. - Args: - stream: Stream to read from. - - Raises: - InvalidDataError: if the sequence was not found. + :param stream: Stream to read from. + :raises: InvalidDataError if the sequence was not found. """ magic = _read(stream, len(MAGIC_BYTES)) if magic != MAGIC_BYTES: - raise InvalidDataError(f'Could not read magic bytes, found {magic!r}') + raise InvalidDataError('Could not read magic bytes, ' + 'found {} : {}'.format(magic, magic.decode())) + diff --git a/fatamorgana/main.py b/fatamorgana/main.py index d0f5c29..3cc721c 100644 --- a/fatamorgana/main.py +++ b/fatamorgana/main.py @@ -3,22 +3,18 @@ This module contains data structures and functions for reading from and writing to whole OASIS layout files, and provides a few additional abstractions for the data contained inside them. """ -from typing import IO import io import logging from . import records -from .records import Modals, Record -from .basic import ( - OffsetEntry, OffsetTable, NString, AString, real_t, Validation, - read_magic_bytes, write_magic_bytes, read_uint, EOFError, - InvalidRecordError, - ) +from .records import Modals +from .basic import OffsetEntry, OffsetTable, NString, AString, real_t, Validation, \ + read_magic_bytes, write_magic_bytes, read_uint, EOFError, \ + InvalidDataError, InvalidRecordError __author__ = 'Jan Petykiewicz' -#logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) @@ -27,21 +23,17 @@ class FileModals: """ File-scoped modal variables """ - cellname_implicit: bool | None = None - propname_implicit: bool | None = None - xname_implicit: bool | None = None - textstring_implicit: bool | None = None - propstring_implicit: bool | None = None + cellname_implicit = None # type: bool or None + propname_implicit = None # type: bool or None + xname_implicit = None # type: bool or None + textstring_implicit = None # type: bool or None + propstring_implicit = None # type: bool or None + cellname_implicit = None # type: bool or None - property_target: list[records.Property] - - within_cell: bool = False - within_cblock: bool = False - end_has_offset_table: bool = False - started: bool = False - - def __init__(self, property_target: list[records.Property]) -> None: - self.property_target = property_target + within_cell = False # type: bool + within_cblock = False # type: bool + end_has_offset_table = None # type: bool + started = False # type: bool class OasisLayout: @@ -51,56 +43,49 @@ class OasisLayout: Names and strings are stored in dicts, indexed by reference number. Layer names and properties are stored directly using their associated record objects. - Cells are stored using `Cell` objects (different from `records.Cell` - record objects). + Cells are stored using Cell objects (different from Cell record objects). + + Properties: + File properties: + .version AString: Version string ('1.0') + .unit real number: grid steps per micron + .validation Validation: checksum data + + Names: + .cellnames Dict[int, NString] + .propnames Dict[int, NString] + .xnames Dict[int, XName] + + Strings: + .textstrings Dict[int, AString] + .propstrings Dict[int, AString] + + Data: + .layers List[records.LayerName] + .properties List[records.Property] + .cells List[Cell] """ - # File properties - version: AString - """File format version string ('1.0')""" + version = None # type: AString + unit = None # type: real_t + validation = None # type: Validation - unit: real_t - """grid steps per micron""" + properties = None # type: List[records.Property] + cells = None # type: List[Cell] - validation: Validation - """checksum data""" + cellnames = None # type: Dict[int, NString] + propnames = None # type: Dict[int, NString] + xnames = None # type: Dict[int, XName] - # Data - properties: list[records.Property] - """Property values""" + textstrings = None # type: Dict[int, AString] + propstrings = None # type: Dict[int, AString] + layers = None # type: List[records.LayerName] - cells: list['Cell'] - """Layout cells""" - layers: list[records.LayerName] - """Layer definitions""" - - # Names - cellnames: dict[int, 'CellName'] - """Cell names""" - - propnames: dict[int, NString] - """Property names""" - - xnames: dict[int, 'XName'] - """Custom names""" - - # String storage - textstrings: dict[int, AString] - """Text strings""" - - propstrings: dict[int, AString] - """Property strings""" - - def __init__( - self, - unit: real_t, - validation: Validation | None = None, - ) -> None: + def __init__(self, unit: real_t, validation: Validation = None): """ - Args: - unit: Real number (i.e. int, float, or `Fraction`), grid steps per micron. - validation: `Validation` object containing checksum data. - Default creates a `Validation` object of the "no checksum" type. + :param unit: Real number (i.e. int, float, or Fraction), grid steps per micron. + :param validation: Validation object containing checksum data. + Default creates a Validation object of the "no checksum" type. """ if validation is None: validation = Validation(0) @@ -118,19 +103,16 @@ class OasisLayout: self.layers = [] @staticmethod - def read(stream: IO[bytes]) -> 'OasisLayout': + def read(stream: io.BufferedIOBase) -> 'OasisLayout': """ - Read an entire .oas file into an `OasisLayout` object. + Read an entire .oas file into an OasisLayout object. - Args: - stream: Stream to read from. - - Returns: - New `OasisLayout` object. + :param stream: Stream to read from. + :return: New OasisLayout object. """ - layout = OasisLayout(unit=-1) # dummy unit + file_state = FileModals() modals = Modals() - file_state = FileModals(layout.properties) + layout = OasisLayout(unit=None) read_magic_bytes(stream) @@ -138,39 +120,32 @@ class OasisLayout: pass return layout - def read_record( - self, - stream: IO[bytes], - modals: Modals, - file_state: FileModals - ) -> bool: + def read_record(self, + stream: io.BufferedIOBase, + modals: Modals, + file_state: FileModals + ) -> bool: """ Read a single record of unspecified type from a stream, adding its - contents into this `OasisLayout` object. + contents into this OasisLayout object. - Args: - stream: Stream to read from. - modals: Modal variable data, used to fill unfilled record - fields and updated using filled record fields. - file_state: File status data. - - Returns: - `True` if EOF was reached without error, `False` otherwise. - - Raises: - InvalidRecordError: from unexpected records - InvalidDataError: from within record parsers + :param stream: Stream to read from. + :param modals: Modal variable data, used to fill unfilled record + fields and updated using filled record fields. + :param file_state: File status data. + :return: True if EOF was reached without error, False otherwise. + :raises: InvalidRecordError from unexpected records; + InvalidDataError from within record parsers. """ try: record_id = read_uint(stream) - except EOFError: + except EOFError as e: if file_state.within_cblock: return True - raise + else: + raise e - logger.info(f'read_record of type {record_id} at position 0x{stream.tell():x}') - - record: Record + logger.info('read_record of type {} at position 0x{:x}'.format(record_id, stream.tell())) # CBlock if record_id == 34: @@ -188,11 +163,12 @@ class OasisLayout: # Make sure order is valid (eg, no out-of-cell geometry) if not file_state.started and record_id != 1: - raise InvalidRecordError(f'Non-Start record {record_id} before Start') + raise InvalidRecordError('Non-Start record {} before Start'.format(record_id)) if record_id == 1: if file_state.started: raise InvalidRecordError('Duplicate Start record') - file_state.started = True + else: + file_state.started = True if record_id == 2 and file_state.within_cblock: raise InvalidRecordError('End within CBlock') @@ -200,28 +176,25 @@ class OasisLayout: pass elif record_id in range(3, 13) or record_id in (28, 29): file_state.within_cell = False - elif record_id in range(15, 28) or record_id in (32, 33): + elif record_id in range(15, 29) or record_id in (32, 33): if not file_state.within_cell: - raise InvalidRecordError('Geometry outside Cell') + raise Exception('Geometry outside Cell') elif record_id in (13, 14): file_state.within_cell = True else: - raise InvalidRecordError(f'Unknown record id: {record_id}') + raise InvalidRecordError('Unknown record id: {}'.format(record_id)) if record_id == 0: - ''' Pad ''' + # Pad pass elif record_id == 1: - ''' Start ''' record = records.Start.read(stream, record_id) record.merge_with_modals(modals) self.unit = record.unit self.version = record.version file_state.end_has_offset_table = record.offset_table is None - file_state.property_target = self.properties # TODO Offset table strict check elif record_id == 2: - ''' End ''' record = records.End.read(stream, record_id, file_state.end_has_offset_table) record.merge_with_modals(modals) self.validation = record.validation @@ -229,7 +202,6 @@ class OasisLayout: raise InvalidRecordError('Stream continues past End record') return True elif record_id in (3, 4): - ''' CellName ''' implicit = record_id == 3 if file_state.cellname_implicit is None: file_state.cellname_implicit = implicit @@ -241,12 +213,8 @@ class OasisLayout: key = record.reference_number if key is None: key = len(self.cellnames) - - cellname = CellName.from_record(record) - self.cellnames[key] = cellname - file_state.property_target = cellname.properties + self.cellnames[key] = record.nstring elif record_id in (5, 6): - ''' TextString ''' implicit = record_id == 5 if file_state.textstring_implicit is None: file_state.textstring_implicit = implicit @@ -260,7 +228,6 @@ class OasisLayout: key = len(self.textstrings) self.textstrings[key] = record.astring elif record_id in (7, 8): - ''' PropName ''' implicit = record_id == 7 if file_state.propname_implicit is None: file_state.propname_implicit = implicit @@ -274,7 +241,6 @@ class OasisLayout: key = len(self.propnames) self.propnames[key] = record.nstring elif record_id in (9, 10): - ''' PropString ''' implicit = record_id == 9 if file_state.propstring_implicit is None: file_state.propstring_implicit = implicit @@ -288,17 +254,17 @@ class OasisLayout: key = len(self.propstrings) self.propstrings[key] = record.astring elif record_id in (11, 12): - ''' LayerName ''' record = records.LayerName.read(stream, record_id) record.merge_with_modals(modals) self.layers.append(record) elif record_id in (28, 29): - ''' Property ''' record = records.Property.read(stream, record_id) record.merge_with_modals(modals) - file_state.property_target.append(record) + if not file_state.within_cell: + self.properties.append(record) + else: + self.cells[-1].properties.append(record) elif record_id in (30, 31): - ''' XName ''' implicit = record_id == 30 if file_state.xname_implicit is None: file_state.xname_implicit = implicit @@ -311,69 +277,53 @@ class OasisLayout: if key is None: key = len(self.xnames) self.xnames[key] = XName.from_record(record) - # TODO: do anything with property target? # # Cell and elements # elif record_id in (13, 14): - ''' Cell ''' record = records.Cell.read(stream, record_id) record.merge_with_modals(modals) - cell = Cell(record.name) - self.cells.append(cell) - file_state.property_target = cell.properties + self.cells.append(Cell(record.name)) elif record_id in (15, 16): - ''' XYMode ''' record = records.XYMode.read(stream, record_id) record.merge_with_modals(modals) elif record_id in (17, 18): - ''' Placement ''' record = records.Placement.read(stream, record_id) record.merge_with_modals(modals) self.cells[-1].placements.append(record) - file_state.property_target = record.properties elif record_id in _GEOMETRY: - ''' Geometry ''' record = _GEOMETRY[record_id].read(stream, record_id) record.merge_with_modals(modals) self.cells[-1].geometry.append(record) - file_state.property_target = record.properties else: - raise InvalidRecordError(f'Unknown record id: {record_id}') + raise InvalidRecordError('Unknown record id: {}'.format(record_id)) return False - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: """ Write this object in OASIS fromat to a stream. - Args: - stream: Stream to write to. - - Returns: - Number of bytes written. - - Raises: - InvalidDataError: if contained records are invalid. + :param stream: Stream to write to. + :return: Number of bytes written. + :raises: InvalidDataError if contained records are invalid. """ modals = Modals() size = 0 size += write_magic_bytes(stream) size += records.Start(self.unit, self.version).dedup_write(stream, modals) - size += sum(p.dedup_write(stream, modals) for p in self.properties) cellnames_offset = OffsetEntry(False, size) - for refnum, cn in self.cellnames.items(): - size += records.CellName(cn.nstring, refnum).dedup_write(stream, modals) - size += sum(p.dedup_write(stream, modals) for p in cn.properties) + size += sum(records.CellName(name, refnum).dedup_write(stream, modals) + for refnum, name in self.cellnames.items()) propnames_offset = OffsetEntry(False, size) size += sum(records.PropName(name, refnum).dedup_write(stream, modals) for refnum, name in self.propnames.items()) xnames_offset = OffsetEntry(False, size) - size += sum(records.XName(x.attribute, x.bstring, refnum).dedup_write(stream, modals) + size += sum(records.XName(x.attribute, x.string, refnum).dedup_write(stream, modals) for refnum, x in self.xnames.items()) textstrings_offset = OffsetEntry(False, size) @@ -387,6 +337,8 @@ class OasisLayout: layernames_offset = OffsetEntry(False, size) size += sum(r.dedup_write(stream, modals) for r in self.layers) + size += sum(p.dedup_write(stream, modals) for p in self.properties) + size += sum(c.dedup_write(stream, modals) for c in self.cells) offset_table = OffsetTable( @@ -404,109 +356,59 @@ class OasisLayout: class Cell: """ Representation of an OASIS cell. + + Properties: + .name NString or int (CellName reference number) + + .properties List of records.Property + .placements List of records.Placement + .geometry List of geometry record objectes """ - name: NString | int - """name or "CellName reference" number""" + name = None # type: NString or int + properties = None # type: List[records.Property] + placements = None # type: List[records.Placement] + geometry = None # type: List[records.geometry_t] - properties: list[records.Property] - placements: list[records.Placement] - geometry: list[records.geometry_t] + def __init__(self, name: NString or int): + """ + :param name: NString or int (CellName reference number) + """ + self.name = name + self.properties = [] + self.placements = [] + self.geometry = [] - def __init__( - self, - name: NString | str | int, - *, - properties: list[records.Property] | None = None, - placements: list[records.Placement] | None = None, - geometry: list[records.geometry_t] | None = None, - ) -> None: - self.name = name if isinstance(name, NString | int) else NString(name) - self.properties = [] if properties is None else properties - self.placements = [] if placements is None else placements - self.geometry = [] if geometry is None else geometry - - def dedup_write(self, stream: IO[bytes], modals: Modals) -> int: + def dedup_write(self, stream: io.BufferedIOBase, modals: Modals) -> int: """ Write this cell to a stream, using the provided modal variables to deduplicate any repeated data. - Args: - stream: Stream to write to. - modals: Modal variables to use for deduplication. - - Returns: - Number of bytes written. - - Raises: - InvalidDataError: if contained records are invalid. + :param stream: Stream to write to. + :param modals: Modal variables to use for deduplication. + :return: Number of bytes written. + :raises: InvalidDataError if contained records are invalid. """ size = records.Cell(self.name).dedup_write(stream, modals) size += sum(p.dedup_write(stream, modals) for p in self.properties) - for placement in self.placements: - size += placement.dedup_write(stream, modals) - size += sum(p.dedup_write(stream, modals) for p in placement.properties) - for shape in self.geometry: - size += shape.dedup_write(stream, modals) - size += sum(p.dedup_write(stream, modals) for p in shape.properties) + size += sum(p.dedup_write(stream, modals) for p in self.placements) + size += sum(g.dedup_write(stream, modals) for g in self.geometry) return size -class CellName: - """ - Representation of a CellName. - - This class is effectively a simplified form of a `records.CellName`, - with the reference data stripped out. - """ - nstring: NString - properties: list[records.Property] - - def __init__( - self, - nstring: NString | str, - properties: list[records.Property] | None = None, - ) -> None: - """ - Args: - nstring: The contained string. - properties: Properties which apply to this CellName's cell, but - are placed following the CellName record. - """ - if isinstance(nstring, NString): - self.nstring = nstring - else: - self.nstring = NString(nstring) - self.properties = [] if properties is None else properties - - @staticmethod - def from_record(record: records.CellName) -> 'CellName': - """ - Create an `CellName` object from a `records.CellName` record. - - Args: - record: CellName record to use. - - Returns: - A new `CellName` object. - """ - return CellName(record.nstring) - - class XName: """ Representation of an XName. - This class is effectively a simplified form of a `records.XName`, + This class is effectively a simplified form of a records.XName, with the reference data stripped out. """ - attribute: int - bstring: bytes + attribute = None # type: int + bstring = None # type: bytes - def __init__(self, attribute: int, bstring: bytes) -> None: + def __init__(self, attribute: int, bstring: bytes): """ - Args: - attribute: Attribute number. - bstring: Binary data. + :param attribute: Attribute number. + :param bstring: Binary data. """ self.attribute = attribute self.bstring = bstring @@ -514,19 +416,16 @@ class XName: @staticmethod def from_record(record: records.XName) -> 'XName': """ - Create an `XName` object from a `records.XName` record. + Create an XName object from a records.XName record. - Args: - record: XName record to use. - - Returns: - a new `XName` object. + :param record: XName record to use. + :return: XName object. """ return XName(record.attribute, record.bstring) # Mapping from record id to record class. -_GEOMETRY: dict[int, type[records.geometry_t]] = { +_GEOMETRY = { 19: records.Text, 20: records.Rectangle, 21: records.Polygon, diff --git a/fatamorgana/py.typed b/fatamorgana/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/fatamorgana/records.py b/fatamorgana/records.py index a137b42..f76682e 100644 --- a/fatamorgana/records.py +++ b/fatamorgana/records.py @@ -10,9 +10,8 @@ Higher-level code (e.g. monitoring for combinations of records with parse, or code for dealing with nested records in a CBlock) should live in main.py instead. """ -from typing import Any, TypeVar, IO, Union, Protocol -from collections.abc import Sequence from abc import ABCMeta, abstractmethod +from typing import List, Dict, Tuple import copy import math import zlib @@ -20,72 +19,66 @@ import io import logging import pprint from warnings import warn -from .basic import ( - AString, NString, repetition_t, property_value_t, real_t, - ReuseRepetition, OffsetTable, Validation, read_point_list, read_property_value, - 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, UnfilledModalError, PathExtensionScheme, _USE_NUMPY, - ) - -if _USE_NUMPY: - import numpy +from .basic import AString, NString, repetition_t, property_value_t, real_t, \ + ReuseRepetition, OffsetTable, Validation, read_point_list, read_property_value, \ + 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 logger = logging.getLogger(__name__) - ''' Type definitions ''' -geometry_t = Union['Text', 'Rectangle', 'Polygon', 'Path', 'Trapezoid', - 'CTrapezoid', 'Circle', 'XElement', 'XGeometry'] -pathextension_t = tuple['PathExtensionScheme', int | None] -point_list_t = Sequence[Sequence[int]] +geometry_t = 'Text' or 'Rectangle' or 'Polygon' or 'Path' or 'Trapezoid' or \ + 'CTrapezoid' or 'Circle' or 'XElement' or 'XGeometry' +pathextension_t = Tuple['PathExtensionScheme' or int] class Modals: """ - Modal variables, used to store data about previously-written or -read records. + Modal variables, used to store data about previously-written ori + -read records. """ - repetition: repetition_t | None = None - placement_x: int = 0 - placement_y: int = 0 - placement_cell: NString | None = None - layer: int | None = None - datatype: int | None = None - text_layer: int | None = None - text_datatype: int | None = None - text_x: int = 0 - text_y: int = 0 - text_string: AString | int | None = None - geometry_x: int = 0 - geometry_y: int = 0 - xy_relative: bool = False - geometry_w: int | None = None - geometry_h: int | None = None - polygon_point_list: point_list_t | None = None - path_half_width: int | None = None - path_point_list: point_list_t | None = None - path_extension_start: pathextension_t | None = None - path_extension_end: pathextension_t | None = None - ctrapezoid_type: int | None = None - circle_radius: int | None = None - property_value_list: Sequence[property_value_t] | None = None - property_name: int | NString | None = None - property_is_standard: bool | None = None + repetition = None # type: repetition_t or None + placement_x = 0 # type: int + placement_y = 0 # type: int + placement_cell = None # type: int or NString or None + layer = None # type: int or None + datatype = None # type: int or None + text_layer = None # type: int or None + text_datatype = None # type: int or None + text_x = 0 # type: int + text_y = 0 # type: int + text_string = None # type: AString or int or None + geometry_x = 0 # type: int + geometry_y = 0 # type: int + xy_relative = False # type: bool + geometry_w = None # type: int or None + geometry_h = None # type: int or None + polygon_point_list = None # type: List[List[int]] or None + path_halfwidth = None # type: int or None + path_point_list = None # type: List[List[int]] or None + path_extension_start = None # type: pathextension_t or None + path_extension_end = None # type: pathextension_t or None + ctrapezoid_type = None # type: int or None + circle_radius = None # type: int or None + property_value_list = None # type: List[property_value_t] or None + property_name = None # type: int or NString or None + property_is_standard = None # type: bool or None - def __init__(self) -> None: + def __init__(self): self.reset() - def reset(self) -> None: + def reset(self): """ Resets all modal variables to their default values. Default values are: - `0` for placement_{x,y}, text_{x,y}, geometry_{x,y} - `False` for xy_relative - Undefined (`None`) for all others + 0 for placement_{x,y}, text_{x,y}, geometry_{x,y} + False for xy_relative + Undefined (None) for all others """ self.repetition = None self.placement_x = 0 @@ -104,7 +97,7 @@ class Modals: self.geometry_w = None self.geometry_h = None self.polygon_point_list = None - self.path_half_width = None + self.path_halfwidth = None self.path_point_list = None self.path_extension_start = None self.path_extension_end = None @@ -115,36 +108,28 @@ class Modals: self.property_is_standard = None -T = TypeVar('T') -def verify_modal(var: T | None) -> T: - if var is None: - raise UnfilledModalError - return var +''' + Records -# -# -# Records -# -# +''' class Record(metaclass=ABCMeta): """ Common interface for records. """ @abstractmethod - def merge_with_modals(self, modals: Modals) -> None: + def merge_with_modals(self, modals: Modals): """ Copy all defined values from this record into the modal variables. Fill all undefined values in this record from the modal variables. - Args: - modals: Modal variables to merge with. + :param modals: Modal variables to merge with. """ pass @abstractmethod - def deduplicate_with_modals(self, modals: Modals) -> None: + def deduplicate_with_modals(self, modals: Modals): """ Check all defined values in this record against those in the modal variables. If any values are equal, remove them from @@ -152,62 +137,46 @@ class Record(metaclass=ABCMeta): used instead. Update the modal variables using the remaining (unequal) values. - Args: - modals: Modal variables to deduplicate with. + :param modals: Modal variables to deduplicate with. """ pass @staticmethod @abstractmethod - def read(stream: IO[bytes], record_id: int) -> 'Record': + def read(stream: io.BufferedIOBase, record_id: int) -> 'Record': """ Read a record of this type from a stream. This function does not merge with modal variables. - Args: - stream: Stream to read from. - record_id: Record id of the record to read. The + :param stream: Stream to read from. + :param record_id: Record id of the record to read. The record id is often used to specify which variant of the record is stored. - - Returns: - The record that was read. - - Raises: - InvalidDataError: if the record is malformed. + :return: The record that was read. + :raises: InvalidDataError if the record is malformed. """ pass @abstractmethod - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: """ Write this record to a stream as-is. This function does not merge or deduplicate with modal variables. - Args: - stream: Stream to write to. - - Returns: - Number of bytes written. - - Raises: - InvalidDataError: if the record contains invalid data. + :param stream: Stream to write to. + :return: Number of bytes written. + :raises: InvalidDataError if the record contains invalid data. """ pass - def dedup_write(self, stream: IO[bytes], modals: Modals) -> int: + def dedup_write(self, stream: io.BufferedIOBase, modals: Modals) -> int: """ - Run `.deduplicate_with_modals()` and then `.write()` to the stream. + Run .deduplicate_with_modals() and then .write() to the stream. - Args: - stream: Stream to write to. - modals: Modal variables to merge with. - - Returns: - Number of bytes written. - - Raises: - InvalidDataError: if the record contains invalid data. + :param stream: Stream to write to. + :param modals: Modal variables to merge with. + :return: Number of bytes written + :raises: InvalidDataError if the record contains invalid data. """ # TODO logging #print(type(self), stream.tell()) @@ -218,227 +187,188 @@ class Record(metaclass=ABCMeta): """ Perform a deep copy of this record. - Returns: - A deep copy of this record. + :return: A deep copy of this record. """ return copy.deepcopy(self) def __repr__(self) -> str: - return f'{self.__class__}: ' + pprint.pformat(self.__dict__) + return '{}: {}'.format(self.__class__, pprint.pformat(self.__dict__)) -class HasRepetition(Protocol): - repetition: repetition_t | None - - -class HasXY(Protocol): - x: int | None - y: int | None - - -class GeometryMixin(metaclass=ABCMeta): - """ - Mixin defining common functions for geometry records - """ - x: int | None - y: int | None - layer: int | None - datatype: int | None - - 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[bytes], - is_present: bool | int, - is_reference: bool | int, - ) -> int | NString | None: +def read_refname(stream: io.BufferedIOBase, + is_present: bool, + is_reference: bool + ) -> None or int or NString: """ Helper function for reading a possibly-absent, possibly-referenced NString. - Args: - stream: Stream to read from. - is_present: If `False`, read nothing and return `None` - is_reference: If `True`, read a uint (reference id), - otherwise read an `NString`. - - Returns: - `None`, reference id, or `NString` + :param stream: Stream to read from. + :param is_present: If False, read nothing and return None + :param is_reference: If True, read a uint (reference id), + otherwise read an NString. + :return: None, reference id, or NString """ if not is_present: return None - if is_reference: + elif is_reference: return read_uint(stream) - return NString.read(stream) + else: + return NString.read(stream) -def read_refstring( - stream: IO[bytes], - is_present: bool | int, - is_reference: bool | int, - ) -> int | AString | None: +def read_refstring(stream: io.BufferedIOBase, + is_present: bool, + is_reference: bool + ) -> None or int or AString: """ - Helper function for reading a possibly-absent, possibly-referenced `AString`. + Helper function for reading a possibly-absent, possibly-referenced AString. - Args: - stream: Stream to read from. - is_present: If `False`, read nothing and return `None` - is_reference: If `True`, read a uint (reference id), - otherwise read an `AString`. - - Returns: - `None`, reference id, or `AString` + :param stream: Stream to read from. + :param is_present: If False, read nothing and return None + :param is_reference: If True, read a uint (reference id), + otherwise read an AString. + :return: None, reference id, or AString """ if not is_present: return None - if is_reference: + elif is_reference: return read_uint(stream) - return AString.read(stream) + else: + return AString.read(stream) class Pad(Record): """ Pad record (ID 0) """ - def merge_with_modals(self, modals: Modals) -> None: + def merge_with_modals(self, modals: Modals): pass - def deduplicate_with_modals(self, modals: Modals) -> None: + def deduplicate_with_modals(self, modals: Modals): pass @staticmethod - def read(stream: IO[bytes], record_id: int) -> 'Pad': + def read(stream: io.BufferedIOBase, record_id: int) -> 'Pad': if record_id != 0: - raise InvalidDataError(f'Invalid record id for Pad {record_id}') + raise InvalidDataError('Invalid record id for Pad ' + '{}'.format(record_id)) record = Pad() - logger.debug(f'Record ending at 0x{stream.tell():x}:\n {record}') + logger.debug('Record ending at 0x{:x}:\n {}'.format(stream.tell(), record)) return record - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: return write_uint(stream, 0) class XYMode(Record): """ XYMode record (ID 15, 16) + + Properties: + .relative (bool, default False) """ - relative: bool + relative = False # type: bool @property def absolute(self) -> bool: - return not self.relative + return not relative @absolute.setter - def absolute(self, b: bool) -> None: + def absolute(self, b: bool): self.relative = not b - def __init__(self, relative: bool) -> None: + def __init__(self, relative: bool): """ - Args: - relative: `True` if the mode is 'relative', `False` if 'absolute'. + :param relative: True if the mode is 'relative', False if 'absolute'. """ self.relative = relative - def merge_with_modals(self, modals: Modals) -> None: + def merge_with_modals(self, modals: Modals): modals.xy_relative = self.relative - def deduplicate_with_modals(self, modals: Modals) -> None: + def deduplicate_with_modals(self, modals: Modals): pass @staticmethod - def read(stream: IO[bytes], record_id: int) -> 'XYMode': + def read(stream: io.BufferedIOBase, record_id: int) -> 'XYMode': if record_id not in (15, 16): raise InvalidDataError('Invalid record id for XYMode') record = XYMode(record_id == 16) - logger.debug(f'Record ending at 0x{stream.tell():x}:\n {record}') + logger.debug('Record ending at 0x{:x}:\n {}'.format(stream.tell(), record)) return record - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: return write_uint(stream, 15 + self.relative) class Start(Record): """ Start Record (ID 1) + + Properties: + .version (AString, "1.0") + .unit (positive real number, grid steps per micron) + .offset_table (OffsetTable or None, if None then table must be + placed in the End record) """ - version: AString - """File format version string""" + version = None # type: AString + unit = None # type: real_t + offset_table = None # type: OffsetTable - unit: real_t - """positive real number, grid steps per micron""" - - offset_table: OffsetTable | None - """If `None` then table must be placed in the `End` record""" - - def __init__( - self, - unit: real_t, - version: AString | str = "1.0", - offset_table: OffsetTable | None = None, - ) -> None: + def __init__(self, + unit: real_t, + version: AString or str = None, + offset_table: OffsetTable = None): """ - Args - unit: Grid steps per micron (positive real number) - version: Version string, default "1.0" - offset_table: `OffsetTable` for the file, or `None` to place - it in the `End` record instead. + :param unit: Grid steps per micron (positive real number) + :param version: Version string, default "1.0" + :param offset_table: OffsetTable for the file, or None to place + it in the End record instead. """ if unit <= 0: - raise InvalidDataError(f'Non-positive unit: {unit}') + raise InvalidDataError('Non-positive unit: {}'.format(unit)) if math.isnan(unit): raise InvalidDataError('NaN unit') if math.isinf(unit): raise InvalidDataError('Non-finite unit') self.unit = unit + if version is None: + version = AString('1.0') if isinstance(version, AString): self.version = version else: self.version = AString(version) if self.version.string != '1.0': - raise InvalidDataError(f'Invalid version string, only "1.0" is allowed: "{self.version.string}"') + raise InvalidDataError('Invalid version string, ' + 'only "1.0" is allowed: ' + + str(self.version.string)) self.offset_table = offset_table - def merge_with_modals(self, modals: Modals) -> None: + def merge_with_modals(self, modals: Modals): modals.reset() - def deduplicate_with_modals(self, modals: Modals) -> None: + def deduplicate_with_modals(self, modals: Modals): modals.reset() @staticmethod - def read(stream: IO[bytes], record_id: int) -> 'Start': + def read(stream: io.BufferedIOBase, record_id: int) -> 'Start': if record_id != 1: - raise InvalidDataError(f'Invalid record id for Start: {record_id}') + raise InvalidDataError('Invalid record id for Start: ' + '{}'.format(record_id)) version = AString.read(stream) unit = read_real(stream) has_offset_table = read_uint(stream) == 0 - offset_table: OffsetTable | None if has_offset_table: offset_table = OffsetTable.read(stream) else: offset_table = None record = Start(unit, version, offset_table) - logger.debug(f'Record ending at 0x{stream.tell():x}:\n {record}') + logger.debug('Record ending at 0x{:x}:\n {}'.format(stream.tell(), record)) return record - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: size = write_uint(stream, 1) size += self.version.write(stream) size += write_real(stream, self.unit) @@ -453,52 +383,50 @@ class End(Record): End record (ID 2) The end record is always padded to a total length of 256 bytes. + + Properties: + .offset_table (OffsetTable or None, None if offset table was + written into the Start record instead) + .validation (Validation object) """ - offset_table: OffsetTable | None - """`None` if offset table was written into the `Start` record instead""" + offset_table = None # type: OffsetTable or None + validation = None # type: Validation - validation: Validation - """object containing checksum""" - - def __init__( - self, - validation: Validation, - offset_table: OffsetTable | None = None, - ) -> None: + def __init__(self, + validation: Validation, + offset_table: OffsetTable = None): """ - Args: - validation: `Validation` object for this file. - offset_table: `OffsetTable`, or `None` if the `Start` record - contained an `OffsetTable`. Default `None`. + :param validation: Validation object for this file. + :param offset_table: OffsetTable, or None if the Start record + contained an OffsetTable. Default None. """ self.validation = validation self.offset_table = offset_table - def merge_with_modals(self, modals: Modals) -> None: + def merge_with_modals(self, modals: Modals): pass - def deduplicate_with_modals(self, modals: Modals) -> None: + def deduplicate_with_modals(self, modals: Modals): pass @staticmethod - def read( - stream: IO[bytes], - record_id: int, - has_offset_table: bool - ) -> 'End': + def read(stream: io.BufferedIOBase, + record_id: int, + has_offset_table: bool + ) -> 'End': if record_id != 2: - raise InvalidDataError(f'Invalid record id for End {record_id}') + raise InvalidDataError('Invalid record id for End {}'.format(record_id)) if has_offset_table: - offset_table: OffsetTable | None = OffsetTable.read(stream) + offset_table = OffsetTable.read(stream) else: offset_table = None - _padding_string = read_bstring(stream) # noqa + _padding_string = read_bstring(stream) validation = Validation.read(stream) record = End(validation, offset_table) - logger.debug(f'Record ending at 0x{stream.tell():x}:\n {record}') + logger.debug('Record ending at 0x{:x}:\n {}'.format(stream.tell(), record)) return record - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: size = write_uint(stream, 2) if self.offset_table is not None: size += self.offset_table.write(stream) @@ -518,53 +446,52 @@ class End(Record): class CBlock(Record): """ CBlock (Compressed Block) record (ID 34) + + Properties: + .compression_type (int, 0 for zlib) + .decompressed_byte_count (int) + .compressed_bytes (bytes) """ - compression_type: int - """ `0` for zlib""" + compression_type = None # type: int + decompressed_byte_count = None # type: int + compressed_bytes = None # type: bytes - decompressed_byte_count: int - """size after decompressing""" - - compressed_bytes: bytes - """compressed data""" - - def __init__( - self, - compression_type: int, - decompressed_byte_count: int, - compressed_bytes: bytes, - ) -> None: + def __init__(self, + compression_type: int, + decompressed_byte_count: int, + compressed_bytes: bytes): """ - Args: - compression_type: `0` (zlib) - decompressed_byte_count: Number of bytes in the decompressed data. - compressed_bytes: The compressed data. + :param compression_type: 0 (zlib) + :param decompressed_byte_count: Number of bytes in the decompressed data. + :param compressed_bytes: The compressed data. """ if compression_type != 0: - raise InvalidDataError(f'CBlock: Invalid compression scheme {compression_type}') + raise InvalidDataError('CBlock: Invalid compression scheme ' + '{}'.format(compression_type)) self.compression_type = compression_type self.decompressed_byte_count = decompressed_byte_count self.compressed_bytes = compressed_bytes - def merge_with_modals(self, modals: Modals) -> None: + def merge_with_modals(self, modals: Modals): pass - def deduplicate_with_modals(self, modals: Modals) -> None: + def deduplicate_with_modals(self, modals: Modals): pass @staticmethod - def read(stream: IO[bytes], record_id: int) -> 'CBlock': + def read(stream: io.BufferedIOBase, record_id: int) -> 'CBlock': if record_id != 34: - raise InvalidDataError(f'Invalid record id for CBlock: {record_id}') + raise InvalidDataError('Invalid record id for CBlock: ' + '{}'.format(record_id)) compression_type = read_uint(stream) decompressed_count = read_uint(stream) compressed_bytes = read_bstring(stream) record = CBlock(compression_type, decompressed_count, compressed_bytes) - logger.debug(f'CBlock ending at 0x{stream.tell():x} was read successfully') + logger.debug('CBlock ending at 0x{:x} was read successfully'.format(stream.tell())) return record - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: size = write_uint(stream, 34) size += write_uint(stream, self.compression_type) size += write_uint(stream, self.decompressed_byte_count) @@ -572,24 +499,18 @@ class CBlock(Record): return size @staticmethod - def from_decompressed( - decompressed_bytes: bytes, - compression_type: int = 0, - compression_args: dict[str, Any] | None = None, - ) -> 'CBlock': + def from_decompressed(decompressed_bytes: bytes, + compression_type: int = 0, + compression_args: Dict = None + ) -> 'CBlock': """ Create a CBlock record from uncompressed data. - Args: - decompressed_bytes: Uncompressed data (one or more non-CBlock records) - compression_type: Compression type (0: zlib). Default `0` - compression_args: Passed as kwargs to `zlib.compressobj()`. Default `{}`. - - Returns: - CBlock object constructed from the data. - - Raises: - InvalidDataError: if invalid `compression_type`. + :param decompressed_bytes: Uncompressed data (one or more non-CBlock records) + :param compression_type: Compression type (0: zlib). Default 0 + :param compression_args Passed as kwargs to zlib.compressobj(). Default {}. + :return: CBlock object constructed from the data. + :raises: InvalidDataError if invalid compression_type. """ if compression_args is None: compression_args = {} @@ -597,60 +518,55 @@ class CBlock(Record): if compression_type == 0: count = len(decompressed_bytes) compressor = zlib.compressobj(wbits=-zlib.MAX_WBITS, **compression_args) - compressed_bytes = (compressor.compress(decompressed_bytes) - + compressor.flush()) + compressed_bytes = compressor.compress(decompressed_bytes) + \ + compressor.flush() else: - raise InvalidDataError(f'Unknown compression type: {compression_type}') + raise InvalidDataError('Unknown compression type: ' + '{}'.format(compression_type)) return CBlock(compression_type, count, compressed_bytes) - def decompress(self, decompression_args: dict[str, Any] | None = None) -> bytes: + def decompress(self, decompression_args: Dict = None) -> bytes: """ Decompress the contents of this CBlock. - Args: - decompression_args: Passed as kwargs to `zlib.decompressobj()`. - - Returns: - Decompressed `bytes` object. - - Raises: - InvalidDataError: if data is malformed or compression type is + :param decompression_args: Passed as kwargs to zlib.decompressobj(). + :return: Decompressed bytes object. + :raises: InvalidDataError if data is malformed or compression type is unknonwn. """ if decompression_args is None: decompression_args = {} if self.compression_type == 0: decompressor = zlib.decompressobj(wbits=-zlib.MAX_WBITS, **decompression_args) - decompressed_bytes = (decompressor.decompress(self.compressed_bytes) - + decompressor.flush()) + decompressed_bytes = decompressor.decompress(self.compressed_bytes) + \ + decompressor.flush() if len(decompressed_bytes) != self.decompressed_byte_count: raise InvalidDataError('Decompressed data length does not match!') else: - raise InvalidDataError(f'Unknown compression type: {self.compression_type}') + raise InvalidDataError('Unknown compression type: ' + '{}'.format(self.compression_type)) return decompressed_bytes class CellName(Record): """ CellName record (ID 3, 4) + + Properties: + .nstring (NString) + .reference_number (int or None) """ - nstring: NString - """name string""" + nstring = None # type: NString + reference_number = None # type: int or None - reference_number: int | None - """`None` results in implicit assignment""" - - def __init__( - self, - nstring: str | NString, - reference_number: int | None = None, - ) -> None: + def __init__(self, + nstring: NString or str, + reference_number: int = None): """ - Args: - nstring: The contained string. - reference_number: Reference id number for the string. - Default is to use an implicitly-assigned number. + :param nstring: The contained string. + :param reference_number: Reference id number for the string. + Default is to use an implicitly-assigned number. """ if isinstance(nstring, NString): self.nstring = nstring @@ -658,26 +574,27 @@ class CellName(Record): self.nstring = NString(nstring) self.reference_number = reference_number - def merge_with_modals(self, modals: Modals) -> None: + def merge_with_modals(self, modals: Modals): modals.reset() - def deduplicate_with_modals(self, modals: Modals) -> None: + def deduplicate_with_modals(self, modals: Modals): modals.reset() @staticmethod - def read(stream: IO[bytes], record_id: int) -> 'CellName': + def read(stream: io.BufferedIOBase, record_id: int) -> 'CellName': if record_id not in (3, 4): - raise InvalidDataError(f'Invalid record id for CellName {record_id}') + raise InvalidDataError('Invalid record id for CellName ' + '{}'.format(record_id)) nstring = NString.read(stream) if record_id == 4: - reference_number: int | None = read_uint(stream) + reference_number = read_uint(stream) else: reference_number = None record = CellName(nstring, reference_number) - logger.debug(f'Record ending at 0x{stream.tell():x}:\n {record}') + logger.debug('Record ending at 0x{:x}:\n {}'.format(stream.tell(), record)) return record - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: record_id = 3 + (self.reference_number is not None) size = write_uint(stream, record_id) size += self.nstring.write(stream) @@ -688,23 +605,21 @@ class CellName(Record): class PropName(Record): """ PropName record (ID 7, 8) + + Properties: + .nstring (NString) + .reference_number (int or None) """ - nstring: NString - """name string""" + nstring = None # type: NString + reference_number = None # type: int or None - reference_number: int | None = None - """`None` results in implicit assignment""" - - def __init__( - self, - nstring: str | NString, - reference_number: int | None = None, - ) -> None: + def __init__(self, + nstring: NString or str, + reference_number: int = None): """ - Args: - nstring: The contained string. - reference_number: Reference id number for the string. - Default is to use an implicitly-assigned number. + :param nstring: The contained string. + :param reference_number: Reference id number for the string. + Default is to use an implicitly-assigned number. """ if isinstance(nstring, NString): self.nstring = nstring @@ -712,26 +627,27 @@ class PropName(Record): self.nstring = NString(nstring) self.reference_number = reference_number - def merge_with_modals(self, modals: Modals) -> None: + def merge_with_modals(self, modals: Modals): modals.reset() - def deduplicate_with_modals(self, modals: Modals) -> None: + def deduplicate_with_modals(self, modals: Modals): modals.reset() @staticmethod - def read(stream: IO[bytes], record_id: int) -> 'PropName': + def read(stream: io.BufferedIOBase, record_id: int) -> 'PropName': if record_id not in (7, 8): - raise InvalidDataError(f'Invalid record id for PropName {record_id}') + raise InvalidDataError('Invalid record id for PropName ' + '{}'.format(record_id)) nstring = NString.read(stream) if record_id == 8: - reference_number: int | None = read_uint(stream) + reference_number = read_uint(stream) else: reference_number = None record = PropName(nstring, reference_number) - logger.debug(f'Record ending at 0x{stream.tell():x}:\n {record}') + logger.debug('Record ending at 0x{:x}:\n {}'.format(stream.tell(), record)) return record - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: record_id = 7 + (self.reference_number is not None) size = write_uint(stream, record_id) size += self.nstring.write(stream) @@ -743,23 +659,21 @@ class PropName(Record): class TextString(Record): """ TextString record (ID 5, 6) + + Properties: + .astring (AString) + .reference_number (int or None) """ - astring: AString - """string contents""" + astring = None # type: AString + reference_number = None # type: int or None - reference_number: int | None = None - """`None` results in implicit assignment""" - - def __init__( - self, - string: AString | str, - reference_number: int | None = None, - ) -> None: + def __init__(self, + string: AString or str, + reference_number: int = None): """ - Args: - string: The contained string. - reference_number: Reference id number for the string. - Default is to use an implicitly-assigned number. + :param string: The contained string. + :param reference_number: Reference id number for the string. + Default is to use an implicitly-assigned number. """ if isinstance(string, AString): self.astring = string @@ -767,26 +681,27 @@ class TextString(Record): self.astring = AString(string) self.reference_number = reference_number - def merge_with_modals(self, modals: Modals) -> None: + def merge_with_modals(self, modals: Modals): modals.reset() - def deduplicate_with_modals(self, modals: Modals) -> None: + def deduplicate_with_modals(self, modals: Modals): modals.reset() @staticmethod - def read(stream: IO[bytes], record_id: int) -> 'TextString': + def read(stream: io.BufferedIOBase, record_id: int) -> 'TextString': if record_id not in (5, 6): - raise InvalidDataError(f'Invalid record id for TextString: {record_id}') + raise InvalidDataError('Invalid record id for TextString: ' + '{}'.format(record_id)) astring = AString.read(stream) if record_id == 6: - reference_number: int | None = read_uint(stream) + reference_number = read_uint(stream) else: reference_number = None record = TextString(astring, reference_number) - logger.debug(f'Record ending at 0x{stream.tell():x}:\n {record}') + logger.debug('Record ending at 0x{:x}:\n {}'.format(stream.tell(), record)) return record - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: record_id = 5 + (self.reference_number is not None) size = write_uint(stream, record_id) size += self.astring.write(stream) @@ -798,23 +713,21 @@ class TextString(Record): class PropString(Record): """ PropString record (ID 9, 10) + + Properties: + .astring (AString) + .reference_number (int or None) """ - astring: AString - """string contents""" + astring = None # type: AString + reference_number = None # type: int or None - reference_number: int | None - """`None` results in implicit assignment""" - - def __init__( - self, - string: AString | str, - reference_number: int | None = None, - ) -> None: + def __init__(self, + string: AString or str, + reference_number: int = None): """ - Args: - string: The contained string. - reference_number: Reference id number for the string. - Default is to use an implicitly-assigned number. + :param string: The contained string. + :param reference_number: Reference id number for the string. + Default is to use an implicitly-assigned number. """ if isinstance(string, AString): self.astring = string @@ -822,26 +735,27 @@ class PropString(Record): self.astring = AString(string) self.reference_number = reference_number - def merge_with_modals(self, modals: Modals) -> None: + def merge_with_modals(self, modals: Modals): modals.reset() - def deduplicate_with_modals(self, modals: Modals) -> None: + def deduplicate_with_modals(self, modals: Modals): modals.reset() @staticmethod - def read(stream: IO[bytes], record_id: int) -> 'PropString': + def read(stream: io.BufferedIOBase, record_id: int) -> 'PropString': if record_id not in (9, 10): - raise InvalidDataError(f'Invalid record id for PropString: {record_id}') + raise InvalidDataError('Invalid record id for PropString: ' + '{}'.format(record_id)) astring = AString.read(stream) if record_id == 10: - reference_number: int | None = read_uint(stream) + reference_number = read_uint(stream) else: reference_number = None record = PropString(astring, reference_number) - logger.debug(f'Record ending at 0x{stream.tell():x}:\n {record}') + logger.debug('Record ending at 0x{:x}:\n {}'.format(stream.tell(), record)) return record - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: record_id = 9 + (self.reference_number is not None) size = write_uint(stream, record_id) size += self.astring.write(stream) @@ -853,32 +767,32 @@ class PropString(Record): class LayerName(Record): """ LayerName record (ID 11, 12) + + Properties: + .nstring (NString) + .layer_interval (Tuple, (int or None, int or None), + bounds on the interval) + .type_interval (Tuple, (int or None, int or None), + bounds on the interval) + .is_textlayer (bool) """ - nstring: NString - """name string""" + nstring = None # type: NString, + layer_interval = None # type: Tuple + type_interval = None # type: Tuple + is_textlayer = None # type: bool - layer_interval: tuple[int | None, int | None] - """bounds on the interval""" - - type_interval: tuple[int | None, int | None] - """bounds on the interval""" - - is_textlayer: bool - """Is this a text layer?""" - - def __init__( - self, - nstring: str | NString, - layer_interval: tuple[int | None, int | None], - type_interval: tuple[int | None, int | None], - is_textlayer: bool, - ) -> None: + def __init__(self, + nstring: NString or str, + layer_interval: Tuple, + type_interval: Tuple, + is_textlayer: bool): """ - Args: - nstring: The layer name. - layer_interval: Tuple giving bounds (or lack of thereof) on the layer number. - type_interval: Tuple giving bounds (or lack of thereof) on the type number. - is_textlayer: `True` if the layer is a text layer. + :param nstring: The layer name. + :param layer_interval: Tuple (int or None, int or None) giving bounds + (or lack of thereof) on the layer number. + :param type_interval: Tuple (int or None, int or None) giving bounds + (or lack of thereof) on the type number. + :param is_textlayer: True if the layer is a text layer. """ if isinstance(nstring, NString): self.nstring = nstring @@ -888,25 +802,26 @@ class LayerName(Record): self.type_interval = type_interval self.is_textlayer = is_textlayer - def merge_with_modals(self, modals: Modals) -> None: + def merge_with_modals(self, modals: Modals): modals.reset() - def deduplicate_with_modals(self, modals: Modals) -> None: + def deduplicate_with_modals(self, modals: Modals): modals.reset() @staticmethod - def read(stream: IO[bytes], record_id: int) -> 'LayerName': + def read(stream: io.BufferedIOBase, record_id: int) -> 'LayerName': if record_id not in (11, 12): - raise InvalidDataError(f'Invalid record id for LayerName: {record_id}') + raise InvalidDataError('Invalid record id for LayerName: ' + '{}'.format(record_id)) is_textlayer = (record_id == 12) nstring = NString.read(stream) layer_interval = read_interval(stream) type_interval = read_interval(stream) record = LayerName(nstring, layer_interval, type_interval, is_textlayer) - logger.debug(f'Record ending at 0x{stream.tell():x}:\n {record}') + logger.debug('Record ending at 0x{:x}:\n {}'.format(stream.tell(), record)) return record - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: record_id = 11 + self.is_textlayer size = write_uint(stream, record_id) size += self.nstring.write(stream) @@ -918,170 +833,163 @@ class LayerName(Record): class Property(Record): """ LayerName record (ID 28, 29) - """ - name: NString | int | None - """`int` is an explicit reference, `None` is a flag to use Modal""" - values: list[property_value_t] | None - is_standard: bool | None - """Whether this is a standard property.""" - def __init__( - self, - name: NString | str | int | None = None, - values: list[property_value_t] | None= None, - is_standard: bool | None = None, - ) -> None: + Properties: + .name (NString or int or None, + int is an explicit reference, + None is a flag to use Modal) + .values (List of property values or None) + .is_standard (bool, whether this is a standard property) + """ + name = None # type: NString or int or None, + values = None # type: List[property_value_t] or None + is_standard = None # type: bool or None + + def __init__(self, + name: NString or str or int = None, + values: List[property_value_t] = None, + is_standard: bool = None): """ - Args: - name: Property name, reference number, or `None` (i.e. use modal) - Default `None. - values: List of property values, or `None` (i.e. use modal) - Default `None`. - is_standard: `True` if this is a standard property. `None` to use modal. - Default `None`. + :param name: Property name, reference number, or None (i.e. use modal) + Default None. + :param values: List of property values, or None (i.e. use modal) + Default None. + :param is_standard: True if this is a standard property. None to use modal. + Default None. """ - if isinstance(name, NString | int) or name is None: - self.name = name - else: + if isinstance(name, str): self.name = NString(name) + else: + self.name = name self.values = values self.is_standard = is_standard - def get_name(self) -> 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) -> None: + def merge_with_modals(self, modals: Modals): adjust_field(self, 'name', modals, 'property_name') adjust_field(self, 'values', modals, 'property_value_list') adjust_field(self, 'is_standard', modals, 'property_is_standard') - def deduplicate_with_modals(self, modals: Modals) -> None: + def deduplicate_with_modals(self, modals: Modals): dedup_field(self, 'name', modals, 'property_name') dedup_field(self, 'values', modals, 'property_value_list') if self.values is None and self.name is None: dedup_field(self, 'is_standard', modals, 'property_is_standard') @staticmethod - def read(stream: IO[bytes], record_id: int) -> 'Property': + def read(stream: io.BufferedIOBase, record_id: int) -> 'Property': if record_id not in (28, 29): - raise InvalidDataError(f'Invalid record id for PropertyValue: {record_id}') + raise InvalidDataError('Invalid record id for PropertyValue: ' + '{}'.format(record_id)) if record_id == 29: record = Property() else: - byte = read_byte(stream) # UUUUVCNS - uu = 0x0f & (byte >> 4) - vv = 0x01 & (byte >> 3) - cc = 0x01 & (byte >> 2) - nn = 0x01 & (byte >> 1) - ss = 0x01 & (byte >> 0) + byte = read_byte(stream) #UUUUVCNS + u = 0x0f & (byte >> 4) + v = 0x01 & (byte >> 3) + c = 0x01 & (byte >> 2) + n = 0x01 & (byte >> 1) + s = 0x01 & (byte >> 0) - name = read_refname(stream, cc, nn) - if vv == 0: - if uu < 0x0f: - value_count = uu + name = read_refname(stream, c, n) + if v == 0: + if u < 0x0f: + value_count = u else: value_count = read_uint(stream) - values: list[property_value_t] | None = [read_property_value(stream) - for _ in range(value_count)] + values = [read_property_value(stream) for _ in range(value_count)] else: values = None -# if uu != 0: -# logger.warning('Malformed property record header; requested modal' -# ' values but had nonzero count. Ignoring count.') - record = Property(name, values, bool(ss)) - logger.debug(f'Record ending at 0x{stream.tell():x}:\n {record}') + if u != 0: + raise InvalidDataError('Malformed property record header') + record = Property(name, values, s) + logger.debug('Record ending at 0x{:x}:\n {}'.format(stream.tell(), record)) return record - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: if self.is_standard is None and self.values is None and self.name is None: return write_uint(stream, 29) - - if self.is_standard is None: - raise InvalidDataError('Property has value or name, but no is_standard flag!') - - if self.values is not None: - value_count = len(self.values) - vv = 0 - uu = 0x0f if value_count >= 0x0f else value_count else: - vv = 1 - uu = 0 - - cc = self.name is not None - nn = cc and isinstance(self.name, int) - ss = self.is_standard - - size = write_uint(stream, 28) - size += write_byte(stream, (uu << 4) | (vv << 3) | (cc << 2) | (nn << 1) | ss) - if cc: - if nn: - size += write_uint(stream, self.name) # type: ignore + if self.is_standard is None: + raise InvalidDataError('Property has value or name, ' + 'but no is_standard flag!') + if self.values is not None: + value_count = len(self.values) + v = 0 + if value_count >= 0x0f: + u = 0x0f + else: + u = value_count else: - size += self.name.write(stream) # type: ignore - if not vv: - if uu == 0x0f: - size += write_uint(stream, len(self.values)) # type: ignore - size += sum(write_property_value(stream, pp) for pp in self.values) # type: ignore + v = 1 + u = 0 + + c = self.name is not None + n = c and isinstance(self.name, int) + s = self.is_standard + + size = write_uint(stream, 28) + size += write_byte(stream, (u << 4) | (v << 3) | (c << 2) | (n << 1) | s) + if c: + if n: + size += write_uint(stream, self.name) + else: + size += self.name.write(stream) + if not v: + if u == 0x0f: + size += write_uint(stream, self.name) + size += sum(write_property_value(stream, p) for p in self.values) return size class XName(Record): """ XName record (ID 30, 31) + + Properties: + .attribute (int) + .bstring (bytes) + .reference_number (int or None, None means to use implicity numbering) """ - attribute: int - """Attribute number""" + attribute = None # type: int + bstring = None # type: bytes + reference_number = None # type: int or None - bstring: bytes - """XName data""" - - reference_number: int | None - """None means to use implicit numbering""" - - def __init__( - self, - attribute: int, - bstring: bytes, - reference_number: int | None = None, - ) -> None: + def __init__(self, + attribute: int, + bstring: bytes, + reference_number: int = None): """ - Args: - attribute: Attribute number. - bstring: Binary XName data. - reference_number: Reference number for this `XName`. - Default `None` (implicit). + :param attribute: Attribute number. + :param bstring: Binary XName data. + :param reference_number: Reference number for this XName. + Default None (implicit). """ self.attribute = attribute self.bstring = bstring self.reference_number = reference_number - def merge_with_modals(self, modals: Modals) -> None: + def merge_with_modals(self, modals: Modals): modals.reset() - def deduplicate_with_modals(self, modals: Modals) -> None: + def deduplicate_with_modals(self, modals: Modals): modals.reset() @staticmethod - def read(stream: IO[bytes], record_id: int) -> 'XName': + def read(stream: io.BufferedIOBase, record_id: int) -> 'XName': if record_id not in (30, 31): - raise InvalidDataError(f'Invalid record id for XName: {record_id}') + raise InvalidDataError('Invalid record id for XName: ' + '{}'.format(record_id)) attribute = read_uint(stream) bstring = read_bstring(stream) if record_id == 31: - reference_number: int | None = read_uint(stream) + reference_number = read_uint(stream) else: reference_number = None record = XName(attribute, bstring, reference_number) - logger.debug(f'Record ending at 0x{stream.tell():x}:\n {record}') + logger.debug('Record ending at 0x{:x}:\n {}'.format(stream.tell(), record)) return record - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: record_id = 30 + (self.reference_number is not None) size = write_uint(stream, record_id) size += write_uint(stream, self.attribute) @@ -1094,92 +1002,83 @@ class XName(Record): class XElement(Record): """ XElement record (ID 32) + + Properties: + .attribute (int) + .bstring (bytes) """ - attribute: int - """Attribute number""" + attribute = None # type: int + bstring = None # type: bytes - bstring: bytes - """XElement data""" - - properties: list['Property'] - - def __init__( - self, - attribute: int, - bstring: bytes, - properties: list['Property'] | None = None, - ) -> None: + def __init__(self, attribute: int, bstring: bytes): """ - Args: - attribute: Attribute number. - bstring: Binary data for this XElement. - properties: List of property records associated with this record. + :param attribute: Attribute number. + :param bstring: Binary data for this XElement. """ self.attribute = attribute self.bstring = bstring - self.properties = [] if properties is None else properties - def merge_with_modals(self, modals: Modals) -> None: + def merge_with_modals(self, modals: Modals): pass - def deduplicate_with_modals(self, modals: Modals) -> None: + def deduplicate_with_modals(self, modals: Modals): pass @staticmethod - def read(stream: IO[bytes], record_id: int) -> 'XElement': + def read(stream: io.BufferedIOBase, record_id: int) -> 'XElement': if record_id != 32: - raise InvalidDataError(f'Invalid record id for XElement: {record_id}') + raise InvalidDataError('Invalid record id for XElement: ' + '{}'.format(record_id)) attribute = read_uint(stream) bstring = read_bstring(stream) record = XElement(attribute, bstring) - logger.debug(f'Record ending at 0x{stream.tell():x}:\n {record}') + logger.debug('Record ending at 0x{:x}:\n {}'.format(stream.tell(), record)) return record - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: size = write_uint(stream, 32) size += write_uint(stream, self.attribute) size += write_bstring(stream, self.bstring) return size -class XGeometry(Record, GeometryMixin): +class XGeometry(Record): """ XGeometry record (ID 33) + + Properties: + .attribute (int) + .bstring (bytes) + .layer (int or None, None means reuse modal) + .datatype (int or None, None means reuse modal) + .x (int or None, None means reuse modal) + .y (int or None, None means reuse modal) + .repetition (reptetition or None) """ - attribute: int - """Attribute number""" + attribute = None # type: int + bstring = None # type: bytes + layer = None # type: int or None + datatype = None # type: int or None + x = None # type: int or None + y = None # type: int or None + repetition = None # type: repetition_t or None - bstring: bytes - """XGeometry data""" - - layer: int | None = None - datatype: int | None = None - x: int | None = None - y: int | None = None - repetition: repetition_t | None = None - properties: list['Property'] - - def __init__( - self, - attribute: int, - bstring: bytes, - layer: int | None = None, - datatype: int | None = None, - x: int | None = None, - y: int | None = None, - repetition: repetition_t | None = None, - properties: list['Property'] | None = None, - ) -> None: + def __init__(self, + attribute: int, + bstring: bytes, + layer: int = None, + datatype: int = None, + x: int = None, + y: int = None, + repetition: repetition_t = None): """ - Args: - attribute: Attribute number for this XGeometry. - bstring: Binary data for this XGeometry. - layer: Layer number. Default `None` (reuse modal). - datatype: Datatype number. Default `None` (reuse modal). - x: X-offset. Default `None` (use modal). - y: Y-offset. Default `None` (use modal). - repetition: Repetition. Default `None` (no repetition). - properties: List of property records associated with this record. + :param attribute: Attribute number for this XGeometry. + :param bstring: Binary data for this XGeometry. + :param layer: Layer number. Default None (reuse modal). + :param datatype: Datatype number. Default None (reuse modal). + :param x: X-offset. Default None (use modal). + :param y: Y-offset. Default None (use modal). + :param repetition: Repetition. Default None (no repetition). """ self.attribute = attribute self.bstring = bstring @@ -1188,104 +1087,105 @@ class XGeometry(Record, GeometryMixin): self.x = x self.y = y self.repetition = repetition - self.properties = [] if properties is None else properties - def merge_with_modals(self, modals: Modals) -> None: + def merge_with_modals(self, modals: Modals): adjust_coordinates(self, modals, 'geometry_x', 'geometry_y') adjust_repetition(self, modals) adjust_field(self, 'layer', modals, 'layer') adjust_field(self, 'datatype', modals, 'datatype') - def deduplicate_with_modals(self, modals: Modals) -> None: + def deduplicate_with_modals(self, modals: Modals): dedup_coordinates(self, modals, 'geometry_x', 'geometry_y') dedup_repetition(self, modals) dedup_field(self, 'layer', modals, 'layer') dedup_field(self, 'datatype', modals, 'datatype') @staticmethod - def read(stream: IO[bytes], record_id: int) -> 'XGeometry': + def read(stream: io.BufferedIOBase, record_id: int) -> 'XGeometry': if record_id != 33: - raise InvalidDataError(f'Invalid record id for XGeometry: {record_id}') + raise InvalidDataError('Invalid record id for XGeometry: ' + '{}'.format(record_id)) - z0, z1, z2, xx, yy, rr, dd, ll = read_bool_byte(stream) + z0, z1, z2, x, y, r, d, l = read_bool_byte(stream) if z0 or z1 or z2: raise InvalidDataError('Malformed XGeometry header') attribute = read_uint(stream) - optional: dict[str, Any] = {} - if ll: + optional = {} + if l: optional['layer'] = read_uint(stream) - if dd: + if d: optional['datatype'] = read_uint(stream) bstring = read_bstring(stream) - if xx: + if x: optional['x'] = read_sint(stream) - if yy: + if y: optional['y'] = read_sint(stream) - if rr: + if r: optional['repetition'] = read_repetition(stream) record = XGeometry(attribute, bstring, **optional) - logger.debug(f'Record ending at 0x{stream.tell():x}:\n {record}') + logger.debug('Record ending at 0x{:x}:\n {}'.format(stream.tell(), record)) return record - def write(self, stream: IO[bytes]) -> int: - xx = self.x is not None - yy = self.y is not None - rr = self.repetition is not None - dd = self.datatype is not None - ll = self.layer is not None + def write(self, stream: io.BufferedIOBase) -> int: + x = self.x is not None + y = self.y is not None + r = self.repetition is not None + d = self.datatype is not None + l = self.layer is not None size = write_uint(stream, 33) - size += write_bool_byte(stream, (0, 0, 0, xx, yy, rr, dd, ll)) + size += write_bool_byte(stream, (0, 0, 0, x, y, r, d, l)) size += write_uint(stream, self.attribute) - if ll: - size += write_uint(stream, self.layer) # type: ignore - if dd: - size += write_uint(stream, self.datatype) # type: ignore + if l: + size += write_uint(stream, self.layer) + if d: + size += write_uint(stream, self.datatype) size += write_bstring(stream, self.bstring) - if xx: - size += write_sint(stream, self.x) # type: ignore - if yy: - size += write_sint(stream, self.y) # type: ignore - if rr: - size += self.repetition.write(stream) # type: ignore + if x: + size += write_sint(stream, self.x) + if y: + size += write_sint(stream, self.y) + if r: + size += self.repetition.write(stream) return size class Cell(Record): """ Cell record (ID 13, 14) + + Properties: + .name (NString or int specifying CellName reference number) """ - name: int | NString - """int specifies "CellName reference" number""" + name = None # type: int or NString - def __init__(self, name: int | str | NString) -> None: + def __init__(self, name: int or NString): """ - Args: - name: `NString`, or an int specifying a `CellName` reference number. + :param name: NString, or an int specifying a CellName reference number. """ - self.name = name if isinstance(name, int | NString) else NString(name) + self.name = name - def merge_with_modals(self, modals: Modals) -> None: + def merge_with_modals(self, modals: Modals): modals.reset() - def deduplicate_with_modals(self, modals: Modals) -> None: + def deduplicate_with_modals(self, modals: Modals): modals.reset() @staticmethod - def read(stream: IO[bytes], record_id: int) -> 'Cell': - name: int | NString + def read(stream: io.BufferedIOBase, record_id: int) -> 'Cell': if record_id == 13: name = read_uint(stream) elif record_id == 14: name = NString.read(stream) else: - raise InvalidDataError(f'Invalid record id for Cell: {record_id}') + raise InvalidDataError('Invalid record id for Cell: ' + '{}'.format(record_id)) record = Cell(name) - logger.debug(f'Record ending at 0x{stream.tell():x}:\n {record}') + logger.debug('Record ending at 0x{:x}:\n {}'.format(stream.tell(), record)) return record - def write(self, stream: IO[bytes]) -> int: + def write(self, stream: io.BufferedIOBase) -> int: size = 0 if isinstance(self.name, int): size += write_uint(stream, 13) @@ -1299,47 +1199,45 @@ class Cell(Record): class Placement(Record): """ Placement record (ID 17, 18) + + Properties: + .attribute (int) + .name (NString, name or + int, CellName reference number or + None, reuse modal) + .magnification (real) + .angle (real, degrees counterclockwise) + .x (int or None, None means reuse modal) + .y (int or None, None means reuse modal) + .repetition (reptetition or None) + .flip (bool) """ - name: int | NString | None = None - """name, "CellName reference" number, or reuse modal""" + name = None # type: NString or int or None + magnification = None # type: real_t or None + angle = None # type: real_t or None + x = None # type: int or None + y = None # type: int or None + repetition = None # type: repetition_t or None + flip = None # type: bool - magnification: real_t | None = None - """magnification factor""" - - angle: real_t | None = None - """Rotation, degrees counterclockwise""" - - x: int | None = None - y: int | None = None - repetition: repetition_t | None = None - flip: bool - """Whether to perform reflection about the x-axis""" - - properties: list['Property'] - - def __init__( - self, - flip: bool, - name: NString | str | int | None = None, - magnification: real_t | None = None, - angle: real_t | None = None, - x: int | None = None, - y: int | None = None, - repetition: repetition_t | None = None, - properties: list['Property'] | None = None, - ) -> None: + def __init__(self, + flip: bool, + name: NString or str or int = None, + magnification: real_t = None, + angle: real_t = None, + x: int = None, + y: int = None, + repetition: repetition_t = None): """ - Args: - flip: Whether to perform reflection about the x-axis. - name: `NString`, an int specifying a `CellName` reference number, - or `None` (reuse modal). - magnification: Magnification factor. Default `None` (use modal). - angle: Rotation angle in degrees, counterclockwise. - Default `None` (reuse modal). - x: X-offset. Default `None` (use modal). - y: Y-offset. Default `None` (use modal). - repetition: Repetition. Default `None` (no repetition). - properties: List of property records associated with this record. + :param flip: Whether to perform reflection about the x-axis. + :param name: NString, an int specifying a CellName reference number, + or None (reuse modal). + :param magnification: Magnification factor. Default None (use modal). + :param angle: Rotation angle in degrees, counterclockwise. + Default None (reuse modal). + :param x: X-offset. Default None (use modal). + :param y: Y-offset. Default None (use modal). + :param repetition: Repetition. Default None (no repetition). """ self.x = x self.y = y @@ -1347,159 +1245,147 @@ class Placement(Record): self.flip = flip self.magnification = magnification self.angle = angle - if isinstance(name, int | NString) or name is None: - self.name = name - else: + if isinstance(name, str): self.name = NString(name) - self.properties = [] if properties is None else properties + else: + self.name = name - def get_name(self) -> 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) -> None: + def merge_with_modals(self, modals: Modals): adjust_coordinates(self, modals, 'placement_x', 'placement_y') adjust_repetition(self, modals) adjust_field(self, 'name', modals, 'placement_cell') - def deduplicate_with_modals(self, modals: Modals) -> None: + def deduplicate_with_modals(self, modals: Modals): dedup_coordinates(self, modals, 'placement_x', 'placement_y') dedup_repetition(self, modals) dedup_field(self, 'name', modals, 'placement_cell') @staticmethod - def read(stream: IO[bytes], record_id: int) -> 'Placement': + def read(stream: io.BufferedIOBase, record_id: int) -> 'Placement': if record_id not in (17, 18): - raise InvalidDataError(f'Invalid record id for Placement: {record_id}') + raise InvalidDataError('Invalid record id for Placement: ' + '{}'.format(record_id)) #CNXYRAAF (17) or CNXYRMAF (18) - cc, nn, xx, yy, rr, ma0, ma1, flip = read_bool_byte(stream) + c, n, x, y, r, ma0, ma1, flip = read_bool_byte(stream) - optional: dict[str, Any] = {} - name = read_refname(stream, cc, nn) + optional = {} + name = read_refname(stream, c, n) if record_id == 17: - aa = int((ma0 << 1) | ma1) + aa = (ma0 << 1) | ma1 optional['angle'] = aa * 90 elif record_id == 18: - mm = ma0 - aa1 = ma1 - if mm: + m = ma0 + a = ma1 + if m: optional['magnification'] = read_real(stream) - if aa1: + if a: optional['angle'] = read_real(stream) - if xx: + if x: optional['x'] = read_sint(stream) - if yy: + if y: optional['y'] = read_sint(stream) - if rr: + if r: optional['repetition'] = read_repetition(stream) record = Placement(flip, name, **optional) - logger.debug(f'Record ending at 0x{stream.tell():x}:\n {record}') + logger.debug('Record ending at 0x{:x}:\n {}'.format(stream.tell(), record)) return record - def write(self, stream: IO[bytes]) -> int: - cc = self.name is not None - nn = cc and isinstance(self.name, int) - xx = self.x is not None - yy = self.y is not None - rr = self.repetition is not None - ff = self.flip + def write(self, stream: io.BufferedIOBase) -> int: + c = self.name is not None + n = c and isinstance(self.name, int) + x = self.x is not None + y = self.y is not None + r = self.repetition is not None + f = self.flip - if (self.magnification == 1 - and self.angle is not None - and abs(self.angle % 90.0) < 1e-14): - aa = int((self.angle / 90) % 4.0) - bools = (cc, nn, xx, yy, rr, aa & 0b10, aa & 0b01, ff) - mm = False - aq = False + if self.angle is not None and self.angle % 90 == 0 and \ + self.magnification is None or self.magnification == 1: + aa = int((self.angle / 90) % 4) + bools = (c, n, x, y, r, aa & 0b10, aa & 0b01, f) + m = False + a = False record_id = 17 else: - mm = self.magnification is not None - aq = self.angle is not None - bools = (cc, nn, xx, yy, rr, mm, aq, ff) + m = self.magnification is not None + a = self.angle is not None + bools = (c, n, x, y, r, m, a, f) record_id = 18 size = write_uint(stream, record_id) size += write_bool_byte(stream, bools) - if cc: - if nn: - size += write_uint(stream, self.name) # type: ignore + if c: + if n: + size += write_uint(stream, self.name) else: - size += self.name.write(stream) # type: ignore - if mm: - size += write_real(stream, self.magnification) # type: ignore - if aq: - size += write_real(stream, self.angle) # type: ignore - if xx: - size += write_sint(stream, self.x) # type: ignore - if yy: - size += write_sint(stream, self.y) # type: ignore - if rr: - size += self.repetition.write(stream) # type: ignore + size += self.name.write(self) + if m: + size += write_real(stream, self.magnification) + if a: + size += write_real(stream, self.angle) + if x: + size += write_sint(stream, self.x) + if y: + size += write_sint(stream, self.y) + if r: + size += self.repetition.write(stream) return size -class Text(Record, GeometryMixin): +class Text(Record): """ Text record (ID 19) - """ - string: AString | int | None = None - layer: int | None = None - datatype: int | None = None - x: int | None = None - y: int | None = None - repetition: repetition_t | None = None - properties: list['Property'] - def __init__( - self, - string: AString | str | int | None = None, - layer: int | None = None, - datatype: int | None = None, - x: int | None = None, - y: int | None = None, - repetition: repetition_t | None = None, - properties: list['Property'] | None = None, - ) -> None: + Properties: + .string (AString or int or None, None means reuse modal) + .layer (int or None, None means reuse modal) + .datatype (int or None, None means reuse modal) + .x (int or None, None means reuse modal) + .y (int or None, None means reuse modal) + .repetition (reptetition or None) + """ + string = None # type: AString or int or None + layer = None # type: int or None + datatype = None # type: int or None + x = None # type: int or None + y = None # type: int or None + repetition = None # type: repetition_t or None + + def __init__(self, + string: AString or str or int = None, + layer: int = None, + datatype: int = None, + x: int = None, + y: int = None, + repetition: repetition_t = None): """ - Args: - string: Text content, or `TextString` reference number. - Default `None` (use modal). - layer: Layer number. Default `None` (reuse modal). - datatype: Datatype number. Default `None` (reuse modal). - x: X-offset. Default `None` (use modal). - y: Y-offset. Default `None` (use modal). - repetition: Repetition. Default `None` (no repetition). - properties: List of property records associated with this record. + :param string: Text content, or TextString reference number. + Default None (use modal). + :param layer: Layer number. Default None (reuse modal). + :param datatype: Datatype number. Default None (reuse modal). + :param x: X-offset. Default None (use modal). + :param y: Y-offset. Default None (use modal). + :param repetition: Repetition. Default None (no repetition). """ self.layer = layer self.datatype = datatype self.x = x self.y = y self.repetition = repetition - if isinstance(string, int | AString) or string is None: - self.string = string - else: + if isinstance(string, str): self.string = AString(string) - self.properties = [] if properties is None else properties + else: + self.string = string - def get_string(self) -> AString | int: - return verify_modal(self.string) # type: ignore - - def merge_with_modals(self, modals: Modals) -> None: + def merge_with_modals(self, modals: Modals): adjust_coordinates(self, modals, 'text_x', 'text_y') adjust_repetition(self, modals) adjust_field(self, 'string', modals, 'text_string') adjust_field(self, 'layer', modals, 'text_layer') adjust_field(self, 'datatype', modals, 'text_datatype') - def deduplicate_with_modals(self, modals: Modals) -> None: + def deduplicate_with_modals(self, modals: Modals): dedup_coordinates(self, modals, 'text_x', 'text_y') dedup_repetition(self, modals) dedup_field(self, 'string', modals, 'text_string') @@ -1507,103 +1393,107 @@ class Text(Record, GeometryMixin): dedup_field(self, 'datatype', modals, 'text_datatype') @staticmethod - def read(stream: IO[bytes], record_id: int) -> 'Text': + def read(stream: io.BufferedIOBase, record_id: int) -> 'Text': if record_id != 19: - raise InvalidDataError(f'Invalid record id for Text: {record_id}') + raise InvalidDataError('Invalid record id for Text: ' + '{}'.format(record_id)) - z0, cc, nn, xx, yy, rr, dd, ll = read_bool_byte(stream) + z0, c, n, x, y, r, d, l = read_bool_byte(stream) if z0: raise InvalidDataError('Malformed Text header') - optional: dict[str, Any] = {} - string = read_refstring(stream, cc, nn) - if ll: + optional = {} + string = read_refstring(stream, c, n) + if l: optional['layer'] = read_uint(stream) - if dd: + if d: optional['datatype'] = read_uint(stream) - if xx: + if x: optional['x'] = read_sint(stream) - if yy: + if y: optional['y'] = read_sint(stream) - if rr: + if r: optional['repetition'] = read_repetition(stream) record = Text(string, **optional) - logger.debug(f'Record ending at 0x{stream.tell():x}:\n {record}') + logger.debug('Record ending at 0x{:x}:\n {}'.format(stream.tell(), record)) return record - def write(self, stream: IO[bytes]) -> int: - cc = self.string is not None - nn = cc and isinstance(self.string, int) - xx = self.x is not None - yy = self.y is not None - rr = self.repetition is not None - dd = self.datatype is not None - ll = self.layer is not None + def write(self, stream: io.BufferedIOBase) -> int: + c = self.string is not None + n = c and isinstance(self.string, int) + x = self.x is not None + y = self.y is not None + r = self.repetition is not None + d = self.datatype is not None + l = self.layer is not None size = write_uint(stream, 19) - size += write_bool_byte(stream, (0, cc, nn, xx, yy, rr, dd, ll)) - if cc: - if nn: - size += write_uint(stream, self.string) # type: ignore + size += write_bool_byte(stream, (0, c, n, x, y, r, d, l)) + if c: + if n: + size += write_uint(stream, self.string) else: - size += self.string.write(stream) # type: ignore - if ll: - size += write_uint(stream, self.layer) # type: ignore - if dd: - size += write_uint(stream, self.datatype) # type: ignore - if xx: - size += write_sint(stream, self.x) # type: ignore - if yy: - size += write_sint(stream, self.y) # type: ignore - if rr: - size += self.repetition.write(stream) # type: ignore + size += self.string.write(self) + if l: + size += write_uint(stream, self.layer) + if d: + size += write_uint(stream, self.datatype) + if x: + size += write_sint(stream, self.x) + if y: + size += write_sint(stream, self.y) + if r: + size += self.repetition.write(stream) return size -class Rectangle(Record, GeometryMixin): +class Rectangle(Record): """ Rectangle record (ID 20) - (x, y) denotes the lower-left (min-x, min-y) corner of the rectangle. + Properties: + .is_square (bool, True if this is a square. + If True, height must be None.) + .width (int or None, None means reuse modal) + .height (int or None, Must be None if .is_square is True. + If .is_square is False, None means reuse modal) + .layer (int or None, None means reuse modal) + .datatype (int or None, None means reuse modal) + .x (int or None, None means use modal) + .y (int or None, None means use modal) + .repetition (reptetition or None) """ - layer: int | None - datatype: int | None - width: int | None - """X-width. `None` means reuse modal""" + layer = None # type: int or None + datatype = None # type: int or None + width = None # type: int or None + height = None # type: int or None + x = None # type: int or None + y = None # type: int or None + repetition = None # type: repetition_t or None + is_square = None # type: bool - height: int | None - """Y-height. Must be `None` if `is_square` is `True`. - If `is_square` is `False`, `None` means reuse modal - """ - - x: int | None - """x-offset of the rectangle's lower-left (min-x) point. - None means reuse modal. - """ - y: int | None - """y-offset of the rectangle's lower-left (min-y) point. - None means reuse modal - """ - - repetition: repetition_t | None - is_square: bool - """If `True`, `height` must be `None`""" - - properties: list['Property'] - - def __init__( - self, - is_square: bool = False, - layer: int | None = None, - datatype: int | None = None, - width: int | None = None, - height: int | None = None, - x: int | None = None, - y: int | None = None, - repetition: repetition_t | None = None, - properties: list['Property'] | None = None, - ) -> None: + def __init__(self, + is_square: bool = False, + layer: int = None, + datatype: int = None, + width: int = None, + height: int = None, + x: int = None, + y: int = None, + repetition: repetition_t = None): + """ + :param is_square: True if this is a square. If True, height must + be None. Default False. + :param layer: Layer number. Default None (reuse modal). + :param datatype: Datatype number. Default None (reuse modal). + :param width: X-width. Default None (reuse modal). + :param height: Y-height. Default None (reuse modal, or use width if + square). Must be None if is_square is True. + :param x: X-offset. Default None (use modal). + :param y: Y-offset. Default None (use modal). + :param repetition: Repetition. Default None (no repetition). + """ self.is_square = is_square self.layer = layer self.datatype = datatype @@ -1614,17 +1504,8 @@ class Rectangle(Record, GeometryMixin): self.repetition = repetition if is_square and self.height is not None: raise InvalidDataError('Rectangle is square and also has height') - self.properties = [] if properties is None else properties - 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) -> None: + def merge_with_modals(self, modals: Modals): adjust_coordinates(self, modals, 'geometry_x', 'geometry_y') adjust_repetition(self, modals) adjust_field(self, 'layer', modals, 'layer') @@ -1635,7 +1516,7 @@ class Rectangle(Record, GeometryMixin): else: adjust_field(self, 'height', modals, 'geometry_h') - def deduplicate_with_modals(self, modals: Modals) -> None: + def deduplicate_with_modals(self, modals: Modals): dedup_coordinates(self, modals, 'geometry_x', 'geometry_y') dedup_repetition(self, modals) dedup_field(self, 'layer', modals, 'layer') @@ -1647,117 +1528,118 @@ class Rectangle(Record, GeometryMixin): dedup_field(self, 'height', modals, 'geometry_h') @staticmethod - def read(stream: IO[bytes], record_id: int) -> 'Rectangle': + def read(stream: io.BufferedIOBase, record_id: int) -> 'Rectangle': if record_id != 20: - raise InvalidDataError(f'Invalid record id for Rectangle: {record_id}') + raise InvalidDataError('Invalid record id for Rectangle: ' + '{}'.format(record_id)) - is_square, ww, hh, xx, yy, rr, dd, ll = read_bool_byte(stream) - optional: dict[str, Any] = {} - if ll: + is_square, w, h, x, y, r, d, l = read_bool_byte(stream) + optional = {} + if l: optional['layer'] = read_uint(stream) - if dd: + if d: optional['datatype'] = read_uint(stream) - if ww: + if w: optional['width'] = read_uint(stream) - if hh: + if h: optional['height'] = read_uint(stream) - if xx: + if x: optional['x'] = read_sint(stream) - if yy: + if y: optional['y'] = read_sint(stream) - if rr: + if r: optional['repetition'] = read_repetition(stream) record = Rectangle(is_square, **optional) - logger.debug(f'Record ending at 0x{stream.tell():x}:\n {record}') + logger.debug('Record ending at 0x{:x}:\n {}'.format(stream.tell(), record)) return record - def write(self, stream: IO[bytes]) -> int: - ss = self.is_square - ww = self.width is not None - hh = self.height is not None - xx = self.x is not None - yy = self.y is not None - rr = self.repetition is not None - dd = self.datatype is not None - ll = self.layer is not None + def write(self, stream: io.BufferedIOBase) -> int: + s = self.is_square + w = self.width is not None + h = self.height is not None + x = self.x is not None + y = self.y is not None + r = self.repetition is not None + d = self.datatype is not None + l = self.layer is not None size = write_uint(stream, 20) - size += write_bool_byte(stream, (ss, ww, hh, xx, yy, rr, dd, ll)) - if ll: - size += write_uint(stream, self.layer) # type: ignore - if dd: - size += write_uint(stream, self.datatype) # type: ignore - if ww: - size += write_uint(stream, self.width) # type: ignore - if hh: - size += write_uint(stream, self.height) # type: ignore - if xx: - size += write_sint(stream, self.x) # type: ignore - if yy: - size += write_sint(stream, self.y) # type: ignore - if rr: - size += self.repetition.write(stream) # type: ignore + size += write_bool_byte(stream, (s, w, h, x, y, r, d, l)) + if l: + size += write_uint(stream, self.layer) + if d: + size += write_uint(stream, self.datatype) + if w: + size += write_uint(stream, self.width) + if h: + size += write_uint(stream, self.height) + if x: + size += write_sint(stream, self.x) + if y: + size += write_sint(stream, self.y) + if r: + size += self.repetition.write(stream) return size -class Polygon(Record, GeometryMixin): +class Polygon(Record): """ Polygon record (ID 21) - """ - layer: int | None - datatype: int | None - x: int | None - """x-offset of the polygon's first point. - None means reuse modal - """ - y: int | None - """y-offset of the polygon's first point. - None means reuse modal - """ - repetition: repetition_t | None - point_list: point_list_t | None - """ - List of offsets between consecutive vertices, starting from the initial - vertex (x, y): `[[dx0, dy0], [dx1, dy1], ...]`. - The list is an implicitly closed path, vertices are [int, int]. - The initial vertex is located at (x, y) and is not represented in `point_list`. - `None` means reuse modal. - """ - properties: list['Property'] + Properties: + .point_list ([[x0, y0], [x1, y1], ...] or None, + list is an implicitly closed path, + vertices are [int, int], + None means reuse modal) + .layer (int or None, None means reuse modal) + .datatype (int or None, None means reuse modal) + .x (int or None, None means reuse modal) + .y (int or None, None means reuse modal) + .repetition (reptetition or None) + """ + layer = None # type: int or None + datatype = None # type: int or None + x = None # type: int or None + y = None # type: int or None + repetition = None # type: repetition_t or None + point_list = None # type: List[List[int]] or None - def __init__( - self, - point_list: point_list_t | None = None, - layer: int | None = None, - datatype: int | None = None, - x: int | None = None, - y: int | None = None, - repetition: repetition_t | None = None, - properties: list['Property'] | None = None, - ) -> None: + def __init__(self, + point_list: List[List[int]] = None, + layer: int = None, + datatype: int = None, + x: int = None, + y: int = None, + repetition: repetition_t = None): + """ + :param point_list: List of vertices [[x0, y0], [x1, y1], ...]. + List forms an implicitly closed path + Default None (reuse modal). + :param layer: Layer number. Default None (reuse modal). + :param datatype: Datatype number. Default None (reuse modal). + :param x: X-offset. Default None (use modal). + :param y: Y-offset. Default None (use modal). + :param repetition: Repetition. Default None (no repetition). + """ self.layer = layer self.datatype = datatype self.x = x self.y = y self.repetition = repetition self.point_list = point_list - self.properties = [] if properties is None else properties - if point_list is not None and len(point_list) < 2: - warn('Polygon with < 3 points', stacklevel=2) + if point_list is not None: + 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) -> None: + def merge_with_modals(self, modals: Modals): adjust_coordinates(self, modals, 'geometry_x', 'geometry_y') adjust_repetition(self, modals) adjust_field(self, 'layer', modals, 'layer') adjust_field(self, 'datatype', modals, 'datatype') adjust_field(self, 'point_list', modals, 'polygon_point_list') - def deduplicate_with_modals(self, modals: Modals) -> None: + def deduplicate_with_modals(self, modals: Modals): dedup_coordinates(self, modals, 'geometry_x', 'geometry_y') dedup_repetition(self, modals) dedup_field(self, 'layer', modals, 'layer') @@ -1765,105 +1647,119 @@ class Polygon(Record, GeometryMixin): dedup_field(self, 'point_list', modals, 'polygon_point_list') @staticmethod - def read(stream: IO[bytes], record_id: int) -> 'Polygon': + def read(stream: io.BufferedIOBase, record_id: int) -> 'Polygon': if record_id != 21: - raise InvalidDataError(f'Invalid record id for Polygon: {record_id}') + raise InvalidDataError('Invalid record id for Polygon: ' + '{}'.format(record_id)) - z0, z1, pp, xx, yy, rr, dd, ll = read_bool_byte(stream) + z0, z1, p, x, y, r, d, l = read_bool_byte(stream) if z0 or z1: raise InvalidDataError('Invalid polygon header') - optional: dict[str, Any] = {} - if ll: + optional = {} + if l: optional['layer'] = read_uint(stream) - if dd: + if d: optional['datatype'] = read_uint(stream) - if pp: - optional['point_list'] = read_point_list(stream, implicit_closed=True) - if xx: + if p: + optional['point_list'] = read_point_list(stream) + if x: optional['x'] = read_sint(stream) - if yy: + if y: optional['y'] = read_sint(stream) - if rr: + if r: optional['repetition'] = read_repetition(stream) record = Polygon(**optional) - logger.debug('Record ending at 0x{stream.tell():x}:\n {record}') + logger.debug('Record ending at 0x{:x}:\n {}'.format(stream.tell(), record)) return record - def write(self, stream: IO[bytes], fast: bool = False) -> int: - pp = self.point_list is not None - xx = self.x is not None - yy = self.y is not None - rr = self.repetition is not None - dd = self.datatype is not None - ll = self.layer is not None + def write(self, stream: io.BufferedIOBase, fast: bool = False) -> int: + p = self.point_list is not None + x = self.x is not None + y = self.y is not None + r = self.repetition is not None + d = self.datatype is not None + l = self.layer is not None size = write_uint(stream, 21) - size += write_bool_byte(stream, (0, 0, pp, xx, yy, rr, dd, ll)) - if ll: - size += write_uint(stream, self.layer) # type: ignore - if dd: - size += write_uint(stream, self.datatype) # type: ignore - if pp: - size += write_point_list(stream, self.point_list, # type: ignore - implicit_closed=True, fast=fast) - if xx: - size += write_sint(stream, self.x) # type: ignore - if yy: - size += write_sint(stream, self.y) # type: ignore - if rr: - size += self.repetition.write(stream) # type: ignore + size += write_bool_byte(stream, (0, 0, p, x, y, r, d, l)) + if l: + size += write_uint(stream, self.layer) + if d: + size += write_uint(stream, self.datatype) + if p: + size += write_point_list(stream, self.point_list, implicit_closed=True, fast=fast) + if x: + size += write_sint(stream, self.x) + if y: + size += write_sint(stream, self.y) + if r: + size += self.repetition.write(stream) return size -class Path(Record, GeometryMixin): +class Path(Record): """ Polygon record (ID 22) - """ - layer: int | None = None - datatype: int | None = None - x: int | None = None - y: int | None = None - repetition: repetition_t | None = None - point_list: point_list_t | None = None - """ - List of offsets between consecutive vertices, starting from the initial - vertex (x, y): `[[dx0, dy0], [dx1, dy1], ...]`. - The initial vertex is located at (x, y) and is not represented in `point_list`. - Offsets are [int, int]; `None` means reuse modal. - """ - half_width: int | None = None - """None means reuse modal""" - - extension_start: pathextension_t | None = None - """ - `None` means reuse modal. - Tuple is of the form (`PathExtensionScheme`, int | None) - Second value is None unless using `PathExtensionScheme.Arbitrary` - Value determines extension past start point. + Properties: + .point_list ([[x0, y0], [x1, y1], ...] or None, + vertices are [int, int], + None means reuse modal) + .half_width (int or None, None means reuse modal) + .extension_start (Tuple or None, + None means reuse modal, + Tuple is of the form + (PathExtensionScheme, int or None) + second value is None unless using + PathExtensionScheme.Arbitrary + Value determines extension past start point. + .extension_end Same form as extension_end. Value determines + extension past end point. + .layer (int or None, None means reuse modal) + .datatype (int or None, None means reuse modal) + .x (int or None, None means use modal) + .y (int or None, None means use modal) + .repetition (reptetition or None) """ + layer = None # type: int or None + datatype = None # type: int or None + x = None # type: int or None + y = None # type: int or None + repetition = None # type: repetition_t or None + point_list = None # type: List[List[int]] or None + half_width = None # type: int or None + extension_start = None # type: pathextension_t or None + extension_end = None # type: pathextension_t or None - extension_end: pathextension_t | None = None - """ - Same form as `extension_end`. Value determines extension past end point. - """ - - properties: list['Property'] - - def __init__( - self, - point_list: point_list_t | None = None, - half_width: int | None = None, - extension_start: pathextension_t | None = None, - extension_end: pathextension_t | None = None, - layer: int | None = None, - datatype: int | None = None, - x: int | None = None, - y: int | None = None, - repetition: repetition_t | None = None, - properties: list['Property'] | None = None, - ) -> None: + def __init__(self, + point_list: List[List[int]] = None, + half_width: int = None, + extension_start: pathextension_t = None, + extension_end: pathextension_t = None, + layer: int = None, + datatype: int = None, + x: int = None, + y: int = None, + repetition: repetition_t = None): + """ + :param point_list: List of vertices [[x0, y0], [x1, y1], ...]. + Default None (reuse modal). + :param half_width: Half-width of the path. Default None (reuse modal). + :param extension_start: Specification for path extension at start of path. + None or Tuple: (PathExtensionScheme, int or None). + int is used only for PathExtensionScheme.Arbitrary. + Default None (reuse modal). + :param extension_end: Specification for path extension at end of path. + None or Tuple: (PathExtensionScheme, int or None). + int is used only for PathExtensionScheme.Arbitrary. + Default None (reuse modal). + :param layer: Layer number. Default None (reuse modal). + :param datatype: Datatype number. Default None (reuse modal). + :param x: X-offset. Default None (use modal). + :param y: Y-offset. Default None (use modal). + :param repetition: Repetition. Default None (no repetition). + """ self.layer = layer self.datatype = datatype self.x = x @@ -1873,21 +1769,8 @@ class Path(Record, GeometryMixin): self.half_width = half_width self.extension_start = extension_start self.extension_end = extension_end - self.properties = [] if properties is None else properties - 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) -> None: + def merge_with_modals(self, modals: Modals): adjust_coordinates(self, modals, 'geometry_x', 'geometry_y') adjust_repetition(self, modals) adjust_field(self, 'layer', modals, 'layer') @@ -1897,7 +1780,7 @@ class Path(Record, GeometryMixin): adjust_field(self, 'extension_start', modals, 'path_extension_start') adjust_field(self, 'extension_end', modals, 'path_extension_end') - def deduplicate_with_modals(self, modals: Modals) -> None: + def deduplicate_with_modals(self, modals: Modals): dedup_coordinates(self, modals, 'geometry_x', 'geometry_y') dedup_repetition(self, modals) dedup_field(self, 'layer', modals, 'layer') @@ -1908,67 +1791,67 @@ class Path(Record, GeometryMixin): dedup_field(self, 'extension_end', modals, 'path_extension_end') @staticmethod - def read(stream: IO[bytes], record_id: int) -> 'Path': + def read(stream: io.BufferedIOBase, record_id: int) -> 'Path': if record_id != 22: - raise InvalidDataError(f'Invalid record id for Path: {record_id}') + raise InvalidDataError('Invalid record id for Path: ' + '{}'.format(record_id)) - ee, ww, pp, xx, yy, rr, dd, ll = read_bool_byte(stream) - optional: dict[str, Any] = {} - if ll: + e, w, p, x, y, r, d, l = read_bool_byte(stream) + optional = {} + if l: optional['layer'] = read_uint(stream) - if dd: + if d: optional['datatype'] = read_uint(stream) - if ww: + if w: optional['half_width'] = read_uint(stream) - if ee: + if e: scheme = read_uint(stream) scheme_end = scheme & 0b11 scheme_start = (scheme >> 2) & 0b11 - def get_pathext(ext_scheme: int) -> pathextension_t | None: + def get_pathext(ext_scheme: int) -> pathextension_t: if ext_scheme == 0: return None - if ext_scheme == 1: + elif ext_scheme == 1: return PathExtensionScheme.Flush, None - if ext_scheme == 2: + elif ext_scheme == 2: return PathExtensionScheme.HalfWidth, None - if ext_scheme == 3: + elif ext_scheme == 3: return PathExtensionScheme.Arbitrary, read_sint(stream) - raise InvalidDataError(f'Invalid ext_scheme: {ext_scheme}') optional['extension_start'] = get_pathext(scheme_start) optional['extension_end'] = get_pathext(scheme_end) - if pp: - optional['point_list'] = read_point_list(stream, implicit_closed=False) - if xx: + if p: + optional['point_list'] = read_point_list(stream) + if x: optional['x'] = read_sint(stream) - if yy: + if y: optional['y'] = read_sint(stream) - if rr: + if r: optional['repetition'] = read_repetition(stream) record = Path(**optional) - logger.debug(f'Record ending at 0x{stream.tell():x}:\n {record}') + logger.debug('Record ending at 0x{:x}:\n {}'.format(stream.tell(), record)) return record - def write(self, stream: IO[bytes], fast: bool = False) -> int: - ee = self.extension_start is not None or self.extension_end is not None - ww = self.half_width is not None - pp = self.point_list is not None - xx = self.x is not None - yy = self.y is not None - rr = self.repetition is not None - dd = self.datatype is not None - ll = self.layer is not None + def write(self, stream: io.BufferedIOBase, fast: bool = False) -> int: + e = self.extension_start is not None or self.extension_end is not None + w = self.half_width is not None + p = self.point_list is not None + x = self.x is not None + y = self.y is not None + r = self.repetition is not None + d = self.datatype is not None + l = self.layer is not None - size = write_uint(stream, 22) - size += write_bool_byte(stream, (ee, ww, pp, xx, yy, rr, dd, ll)) - if ll: - size += write_uint(stream, self.layer) # type: ignore - if dd: - size += write_uint(stream, self.datatype) # type: ignore - if ww: - size += write_uint(stream, self.half_width) # type: ignore - if ee: + size = write_uint(stream, 21) + size += write_bool_byte(stream, (e, w, p, x, y, r, d, l)) + if l: + size += write_uint(stream, self.layer) + if d: + size += write_uint(stream, self.datatype) + if w: + size += write_uint(stream, self.half_width) + if e: scheme = 0 if self.extension_start is not None: scheme += self.extension_start[0].value << 2 @@ -1976,88 +1859,89 @@ class Path(Record, GeometryMixin): scheme += self.extension_end[0].value size += write_uint(stream, scheme) if scheme & 0b1100 == 0b1100: - size += write_sint(stream, self.extension_start[1]) # type: ignore + size += write_sint(stream, self.extension_start[1]) if scheme & 0b0011 == 0b0011: - size += write_sint(stream, self.extension_end[1]) # type: ignore - if pp: - size += write_point_list(stream, self.point_list, # type: ignore - implicit_closed=False, fast=fast) - if xx: - size += write_sint(stream, self.x) # type: ignore - if yy: - size += write_sint(stream, self.y) # type: ignore - if rr: - size += self.repetition.write(stream) # type: ignore + size += write_sint(stream, self.extension_end[1]) + if p: + size += write_point_list(stream, self.point_list, implicit_closed=False, fast=fast) + if x: + size += write_sint(stream, self.x) + if y: + size += write_sint(stream, self.y) + if r: + size += self.repetition.write(stream) return size -class Trapezoid(Record, GeometryMixin): +class Trapezoid(Record): """ Trapezoid record (ID 23, 24, 25) - Trapezoid with at least two sides parallel to the x- or y-axis. - (x, y) denotes the lower-left (min-x, min-y) corner of the trapezoid's bounding box. + Properties: + .delta_a (int or None, + If horizontal, signed x-distance from top left + vertex to bottom left vertex. If vertical, signed + y-distance from bottom left vertex to bottom right + vertex. + None means reuse modal.) + .delta_b (int or None, + If horizontal, signed x-distance from bottom right + vertex to top right vertex. If vertical, signed + y-distance from top right vertex to top left vertex. + None means reuse modal.) + .is_vertical (bool, True if the left and right sides are aligned to + the y-axis. If the trapezoid is a rectangle, either + True or False can be used.) + .width (int or None, Bounding box x-width, None means reuse modal) + .height (int or None, Bounding box y-height, None means reuse modal) + .layer (int or None, None means reuse modal) + .datatype (int or None, None means reuse modal) + .x (int or None, None means se modal) + .y (int or None, None means se modal) + .repetition (reptetition or None) """ - layer: int | None = None - datatype: int | None = None - width: int | None = None - """Bounding box x-width, None means reuse modal.""" + layer = None # type: int or None + datatype = None # type: int or None + width = None # type: int or None + height = None # type: int or None + x = None # type: int or None + y = None # type: int or None + repetition = None # type: repetition_t or None + delta_a = None # type: int + delta_b = None # type: int + is_vertical = None # type: bool - height: int | None = None - """Bounding box y-height, None means reuse modal.""" - - x: int | None = None - """x-offset to lower-left corner of the trapezoid's bounding box. - None means reuse modal - """ - - y: int | None = None - """y-offset to lower-left corner of the trapezoid's bounding box. - None means reuse modal - """ - - repetition: repetition_t | None = None - delta_a: int = 0 - """ - If horizontal, signed x-distance from top left vertex to bottom left vertex. - If vertical, signed y-distance from bottom left vertex to bottom right vertex. - None means reuse modal. - """ - - delta_b: int = 0 - """ - If horizontal, signed x-distance from bottom right vertex to top right vertex. - If vertical, signed y-distance from top right vertex to top left vertex. - None means reuse modal. - """ - - is_vertical: bool - """ - `True` if the left and right sides are aligned to the y-axis. - If the trapezoid is a rectangle, either `True` or `False` can be used. - """ - - properties: list['Property'] - - def __init__( - self, - is_vertical: bool, - delta_a: int = 0, - delta_b: int = 0, - layer: int | None = None, - datatype: int | None = None, - width: int | None = None, - height: int | None = None, - x: int | None = None, - y: int | None = None, - repetition: repetition_t | None = None, - properties: list['Property'] | None = None, - ) -> None: + def __init__(self, + is_vertical: bool, + delta_a: int = 0, + delta_b: int = 0, + layer: int = None, + datatype: int = None, + width: int = None, + height: int = None, + x: int = None, + y: int = None, + repetition: repetition_t = None): """ - Raises: - InvalidDataError: if dimensions are impossible. + :param is_vertical: True if both the left and right sides are aligned + to the y-axis. If the trapezoid is a rectangle, either value + is permitted. + :param delta_a: If horizontal, signed x-distance from top-left vertex + to bottom-left vertex. If vertical, signed y-distance from bottom- + left vertex to bottom-right vertex. Default None (reuse modal). + :param delta_b: If horizontal, signed x-distance from bottom-right vertex + to top right vertex. If vertical, signed y-distance from top-right + vertex to top-left vertex. Default None (reuse modal). + :param layer: Layer number. Default None (reuse modal). + :param datatype: Datatype number. Default None (reuse modal). + :param width: X-width of bounding box. Default None (reuse modal). + :param height: Y-height of bounding box. Default None (reuse modal) + :param x: X-offset. Default None (use modal). + :param y: Y-offset. Default None (use modal). + :param repetition: Repetition. Default None (no repetition). + :raises: InvalidDataError if dimensions are impossible. """ - self.is_vertical = bool(is_vertical) + self.is_vertical = is_vertical self.delta_a = delta_a self.delta_b = delta_b self.layer = layer @@ -2067,30 +1951,17 @@ class Trapezoid(Record, GeometryMixin): self.x = x self.y = y self.repetition = repetition - self.properties = [] if properties is None else properties if self.is_vertical: if height is not None and delta_b - delta_a > height: - raise InvalidDataError(f'Trapezoid: h < delta_b - delta_a ({height} < {delta_b} - {delta_a})') - elif width is not None and delta_b - delta_a > width: - raise InvalidDataError(f'Trapezoid: w < delta_b - delta_a ({width} < {delta_b} - {delta_a})') + raise InvalidDataError('Trapezoid: h < delta_b - delta_a' + ' ({} < {} - {})'.format(height, delta_b, delta_a)) + else: + if width is not None and delta_b - delta_a > width: + 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) -> None: + def merge_with_modals(self, modals: Modals): adjust_coordinates(self, modals, 'geometry_x', 'geometry_y') adjust_repetition(self, modals) adjust_field(self, 'layer', modals, 'layer') @@ -2098,7 +1969,7 @@ class Trapezoid(Record, GeometryMixin): adjust_field(self, 'width', modals, 'geometry_w') adjust_field(self, 'height', modals, 'geometry_h') - def deduplicate_with_modals(self, modals: Modals) -> None: + def deduplicate_with_modals(self, modals: Modals): dedup_coordinates(self, modals, 'geometry_x', 'geometry_y') dedup_repetition(self, modals) dedup_field(self, 'layer', modals, 'layer') @@ -2107,43 +1978,44 @@ class Trapezoid(Record, GeometryMixin): dedup_field(self, 'height', modals, 'geometry_h') @staticmethod - def read(stream: IO[bytes], record_id: int) -> 'Trapezoid': + def read(stream: io.BufferedIOBase, record_id: int) -> 'Trapezoid': if record_id not in (23, 24, 25): - raise InvalidDataError(f'Invalid record id for Trapezoid: {record_id}') + raise InvalidDataError('Invalid record id for Trapezoid: ' + '{}'.format(record_id)) - is_vertical, ww, hh, xx, yy, rr, dd, ll = read_bool_byte(stream) - optional: dict[str, Any] = {} - if ll: + is_vertical, w, h, x, y, r, d, l = read_bool_byte(stream) + optional = {} + if l: optional['layer'] = read_uint(stream) - if dd: + if d: optional['datatype'] = read_uint(stream) - if ww: + if w: optional['width'] = read_uint(stream) - if hh: + if h: optional['height'] = read_uint(stream) if record_id != 25: optional['delta_a'] = read_sint(stream) if record_id != 24: optional['delta_b'] = read_sint(stream) - if xx: + if x: optional['x'] = read_sint(stream) - if yy: + if y: optional['y'] = read_sint(stream) - if rr: + if r: optional['repetition'] = read_repetition(stream) - record = Trapezoid(bool(is_vertical), **optional) - logger.debug(f'Record ending at 0x{stream.tell():x}:\n {record}') + record = Trapezoid(is_vertical, **optional) + logger.debug('Record ending at 0x{:x}:\n {}'.format(stream.tell(), record)) return record - def write(self, stream: IO[bytes]) -> int: - vv = self.is_vertical - ww = self.width is not None - hh = self.height is not None - xx = self.x is not None - yy = self.y is not None - rr = self.repetition is not None - dd = self.datatype is not None - ll = self.layer is not None + def write(self, stream: io.BufferedIOBase) -> int: + v = self.is_vertical + w = self.width is not None + h = self.height is not None + x = self.x is not None + y = self.y is not None + r = self.repetition is not None + d = self.datatype is not None + l = self.layer is not None if self.delta_b == 0: record_id = 24 @@ -2152,109 +2024,72 @@ class Trapezoid(Record, GeometryMixin): else: record_id = 23 size = write_uint(stream, record_id) - size += write_bool_byte(stream, (vv, ww, hh, xx, yy, rr, dd, ll)) - if ll: - size += write_uint(stream, self.layer) # type: ignore - if dd: - size += write_uint(stream, self.datatype) # type: ignore - if ww: - size += write_uint(stream, self.width) # type: ignore - if hh: - size += write_uint(stream, self.height) # type: ignore + size += write_bool_byte(stream, (v, w, h, x, y, r, d, l)) + if l: + size += write_uint(stream, self.layer) + if d: + size += write_uint(stream, self.datatype) + if w: + size += write_uint(stream, self.width) + if h: + size += write_uint(stream, self.height) if record_id != 25: - size += write_sint(stream, self.delta_a) # type: ignore + size += write_sint(stream, self.delta_a) if record_id != 24: - size += write_sint(stream, self.delta_b) # type: ignore - if xx: - size += write_sint(stream, self.x) # type: ignore - if yy: - size += write_sint(stream, self.y) # type: ignore - if rr: - size += self.repetition.write(stream) # type: ignore + size += write_sint(stream, self.delta_b) + if x: + size += write_sint(stream, self.x) + if y: + size += write_sint(stream, self.y) + if r: + size += self.repetition.write(stream) return size -class CTrapezoid(Record, GeometryMixin): - r""" +# TODO: CTrapezoid type descriptions +class CTrapezoid(Record): + """ CTrapezoid record (ID 26) - Compact trapezoid formats. - Two sides are assumed to be parallel to the x- or y-axis, and the remaining - sides form 45 or 90 degree angles with them. - - `ctrapezoid_type` is in `range(0, 26)`, with the following shapes: - ____ ____ _____ ______ - | 0 \ / 2 | / 4 \ / 6 / - |_____\ /_____| /_______\ /_____/ - ______ ______ _________ ______ - | 1 / \ 3 | \ 5 / \ 7 \ - |____/ \____| \_____/ \_____\ - w >= h w >= 2h - - ___ ___ |\ /| /| |\ - |\ /| | | | | | \ / | / | | \ - | \ / | |10 | | 11| |12| |13| |14| |15| - | \ / | | / \ | | | | | | | | | - | 8 | | 9 | | / \ | | / \ | | / \ | - |___| |___| |/ \| |/ \| |/ \| - h >= w h >= w h >= 2w h >= 2w - - __________ - |\ /| /\ |\ /| | 24 | (rect) - | \ / | / \ | \ / | |__________| - |16\ /18| / 20 \ |22\ /23| - |___\ /___| /______\ | / \ | - ____ ____ ______ | / \ | - | / \ | \ / |/ \| _____ - |17/ \19| \ 21 / h = 2w | | (sqr) - | / \ | \ / set h = None | 25 | - |/ \| \/ |_____| - w = h w = 2h w = h - set h = None set w = None set h = None - + Properties: + .ctrapezoid_type (int or None, see OASIS spec for details, None means reuse modal) + .width (int or None, Bounding box x-width, None means reuse modal) + .height (int or None, Bounding box y-height, None means reuse modal) + .layer (int or None, None means reuse modal) + .datatype (int or None, None means reuse modal) + .x (int or None, None means se modal) + .y (int or None, None means se modal) + .repetition (reptetition or None) """ - ctrapezoid_type: int | None = None - """See class docstring for details. None means reuse modal.""" + ctrapezoid_type = None # type: int or None + layer = None # type: int or None + datatype = None # type: int or None + width = None # type: int or None + height = None # type: int or None + x = None # type: int or None + y = None # type: int or None + repetition = None # type: repetition_t or None - layer: int | None = None - datatype: int | None = None - width: int | None = None - """width: Bounding box x-width - None means unnecessary, or reuse modal if necessary. - """ - - height: int | None = None - """Bounding box y-height. - None means unnecessary, or reuse modal if necessary. - """ - - x: int | None = None - """x-offset of lower-left (min-x) point of bounding box. - None means reuse modal - """ - y: int | None = None - """y-offset of lower-left (min-y) point of bounding box. - None means reuse modal - """ - - repetition: repetition_t | None = None - properties: list['Property'] - - def __init__( - self, - ctrapezoid_type: int | None = None, - layer: int | None = None, - datatype: int | None = None, - width: int | None = None, - height: int | None = None, - x: int | None = None, - y: int | None = None, - repetition: repetition_t | None = None, - properties: list['Property'] | None = None, - ) -> None: + def __init__(self, + ctrapezoid_type: int = None, + layer: int = None, + datatype: int = None, + width: int = None, + height: int = None, + x: int = None, + y: int = None, + repetition: repetition_t = None): """ - Raises: - InvalidDataError: if dimensions are invalid. + :param ctrapezoid_type: CTrapezoid type; see OASIS format + documentation. Default None (reuse modal). + :param layer: Layer number. Default None (reuse modal). + :param datatype: Datatype number. Default None (reuse modal). + :param width: X-width of bounding box. Default None (reuse modal). + :param height: Y-height of bounding box. Default None (reuse modal) + :param x: X-offset. Default None (use modal). + :param y: Y-offset. Default None (use modal). + :param repetition: Repetition. Default None (no repetition). + :raises: InvalidDataError if dimensions are invalid. """ self.ctrapezoid_type = ctrapezoid_type self.layer = layer @@ -2264,28 +2099,30 @@ class CTrapezoid(Record, GeometryMixin): self.x = x self.y = y self.repetition = repetition - self.properties = [] if properties is None else properties - self.check_valid() + if ctrapezoid_type in (20, 21) and width is not None: + raise InvalidDataError('CTrapezoid has spurious width entry: ' + '{}'.format(width)) + if ctrapezoid_type in (16, 17, 18, 19, 22, 23, 25) and height is not None: + raise InvalidDataError('CTrapezoid has spurious height entry: ' + '{}'.format(height)) + if ctrapezoid_type in range(0, 4) and width < height: + raise InvalidDataError('CTrapezoid has width < height' + ' ({} < {})'.format(width, height)) + if ctrapezoid_type in range(4, 8) and width < 2 * height: + raise InvalidDataError('CTrapezoid has width < 2*height' + ' ({} < 2 * {})'.format(width, height)) + if ctrapezoid_type in range(8, 12) and width > height: + raise InvalidDataError('CTrapezoid has width > height' + ' ({} > {})'.format(width, height)) + if ctrapezoid_type in range(12, 16) and 2 * width > height: + raise InvalidDataError('CTrapezoid has 2*width > height' + ' ({} > 2 * {})'.format(width, height)) + if ctrapezoid_type is not None and ctrapezoid_type not in range(0, 26): + raise InvalidDataError('CTrapezoid has invalid type: ' + '{}'.format(ctrapezoid_type)) - 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) -> None: + def merge_with_modals(self, modals: Modals): adjust_coordinates(self, modals, 'geometry_x', 'geometry_y') adjust_repetition(self, modals) adjust_field(self, 'layer', modals, 'layer') @@ -2294,19 +2131,19 @@ class CTrapezoid(Record, GeometryMixin): if self.ctrapezoid_type in (20, 21): if self.width is not None: - raise InvalidDataError(f'CTrapezoid has spurious width entry: {self.width}') + raise InvalidDataError('CTrapezoid has spurious width entry: ' + '{}'.format(self.width)) else: adjust_field(self, 'width', modals, 'geometry_w') if self.ctrapezoid_type in (16, 17, 18, 19, 22, 23, 25): if self.height is not None: - raise InvalidDataError(f'CTrapezoid has spurious height entry: {self.height}') + raise InvalidDataError('CTrapezoid has spurious height entry: ' + '{}'.format(self.height)) else: adjust_field(self, 'height', modals, 'geometry_h') - self.check_valid() - - def deduplicate_with_modals(self, modals: Modals) -> None: + def deduplicate_with_modals(self, modals: Modals): dedup_coordinates(self, modals, 'geometry_x', 'geometry_y') dedup_repetition(self, modals) dedup_field(self, 'layer', modals, 'layer') @@ -2317,133 +2154,111 @@ class CTrapezoid(Record, GeometryMixin): if self.ctrapezoid_type in (20, 21): if self.width is not None: - raise InvalidDataError(f'CTrapezoid has spurious width entry: {self.width}') + raise InvalidDataError('CTrapezoid has spurious width entry: ' + '{}'.format(self.width)) else: dedup_field(self, 'width', modals, 'geometry_w') if self.ctrapezoid_type in (16, 17, 18, 19, 22, 23, 25): if self.height is not None: - raise InvalidDataError(f'CTrapezoid has spurious height entry: {self.height}') + raise InvalidDataError('CTrapezoid has spurious height entry: ' + '{}'.format(self.height)) else: dedup_field(self, 'height', modals, 'geometry_h') - self.check_valid() - @staticmethod - def read(stream: IO[bytes], record_id: int) -> 'CTrapezoid': + def read(stream: io.BufferedIOBase, record_id: int) -> 'CTrapezoid': if record_id != 26: - raise InvalidDataError(f'Invalid record id for CTrapezoid: {record_id}') + raise InvalidDataError('Invalid record id for CTrapezoid: ' + '{}'.format(record_id)) - tt, ww, hh, xx, yy, rr, dd, ll = read_bool_byte(stream) - optional: dict[str, Any] = {} - if ll: + t, w, h, x, y, r, d, l = read_bool_byte(stream) + optional = {} + if l: optional['layer'] = read_uint(stream) - if dd: + if d: optional['datatype'] = read_uint(stream) - if tt: + if t: optional['ctrapezoid_type'] = read_uint(stream) - if ww: + if w: optional['width'] = read_uint(stream) - if hh: + if h: optional['height'] = read_uint(stream) - if xx: + if x: optional['x'] = read_sint(stream) - if yy: + if y: optional['y'] = read_sint(stream) - if rr: + if r: optional['repetition'] = read_repetition(stream) record = CTrapezoid(**optional) - logger.debug(f'Record ending at 0x{stream.tell():x}:\n {record}') + logger.debug('Record ending at 0x{:x}:\n {}'.format(stream.tell(), record)) return record - def write(self, stream: IO[bytes]) -> int: - tt = self.ctrapezoid_type is not None - ww = self.width is not None - hh = self.height is not None - xx = self.x is not None - yy = self.y is not None - rr = self.repetition is not None - dd = self.datatype is not None - ll = self.layer is not None + def write(self, stream: io.BufferedIOBase) -> int: + t = self.ctrapezoid_type is not None + w = self.width is not None + h = self.height is not None + x = self.x is not None + y = self.y is not None + r = self.repetition is not None + d = self.datatype is not None + l = self.layer is not None size = write_uint(stream, 26) - size += write_bool_byte(stream, (tt, ww, hh, xx, yy, rr, dd, ll)) - if ll: - size += write_uint(stream, self.layer) # type: ignore - if dd: - size += write_uint(stream, self.datatype) # type: ignore - if tt: - size += write_uint(stream, self.ctrapezoid_type) # type: ignore - if ww: - size += write_uint(stream, self.width) # type: ignore - if hh: - size += write_uint(stream, self.height) # type: ignore - if xx: - size += write_sint(stream, self.x) # type: ignore - if yy: - size += write_sint(stream, self.y) # type: ignore - if rr: - size += self.repetition.write(stream) # type: ignore + size += write_bool_byte(stream, (t, w, h, x, y, r, d, l)) + if l: + size += write_uint(stream, self.layer) + if d: + size += write_uint(stream, self.datatype) + if t: + size += write_uint(stream, self.ctrapezoid_type) + if w: + size += write_uint(stream, self.width) + if h: + size += write_uint(stream, self.height) + if x: + size += write_sint(stream, self.x) + if y: + size += write_sint(stream, self.y) + if r: + size += self.repetition.write(stream) return size - def check_valid(self) -> None: - ctrapezoid_type = self.ctrapezoid_type - width = self.width - height = self.height - if ctrapezoid_type in (20, 21) and width is not None: - raise InvalidDataError(f'CTrapezoid has spurious width entry: {width}') - if ctrapezoid_type in (16, 17, 18, 19, 22, 23, 25) and height is not None: - raise InvalidDataError(f'CTrapezoid has spurious height entry: {height}') - - if width is not None and height is not None: - if ctrapezoid_type in range(0, 4) and width < height: # noqa: PIE808 - raise InvalidDataError(f'CTrapezoid has width < height ({width} < {height})') - if ctrapezoid_type in range(4, 8) and width < 2 * height: - raise InvalidDataError(f'CTrapezoid has width < 2*height ({width} < 2 * {height})') - if ctrapezoid_type in range(8, 12) and width > height: - raise InvalidDataError(f'CTrapezoid has width > height ({width} > {height})') - if ctrapezoid_type in range(12, 16) and 2 * width > height: - raise InvalidDataError(f'CTrapezoid has 2*width > height ({width} > 2 * {height})') - - if ctrapezoid_type is not None and ctrapezoid_type not in range(0, 26): # noqa: PIE808 - raise InvalidDataError(f'CTrapezoid has invalid type: {ctrapezoid_type}') - - -class Circle(Record, GeometryMixin): +class Circle(Record): """ Circle record (ID 27) + + Properties: + .radius (int or None, None means reuse modal) + .layer (int or None, None means reuse modal) + .datatype (int or None, None means reuse modal) + .x (int or None, None means se modal) + .y (int or None, None means se modal) + .repetition (reptetition or None) """ - layer: int | None - datatype: int | None - x: int | None - y: int | None - repetition: repetition_t | None - radius: int | None - properties: list['Property'] + layer = None # type: int or None + datatype = None # type: int or None + x = None # type: int or None + y = None # type: int or None + repetition = None # type: repetition_t or None + radius = None # type: int or None - def __init__( - self, - radius: int | None = None, - layer: int | None = None, - datatype: int | None = None, - x: int | None = None, - y: int | None = None, - repetition: repetition_t | None = None, - properties: list['Property'] | None = None, - ) -> None: + def __init__(self, + radius: int = None, + layer: int = None, + datatype: int = None, + x: int = None, + y: int = None, + repetition: repetition_t = None): """ - Args: - radius: Radius. Default `None` (reuse modal). - layer: Layer number. Default `None` (reuse modal). - datatype: Datatype number. Default `None` (reuse modal). - x: X-offset. Default `None` (use modal). - y: Y-offset. Default `None` (use modal). - repetition: Repetition. Default `None` (no repetition). - properties: List of property records associated with this record. - - Raises: - InvalidDataError: if dimensions are invalid. + :param radius: Radius. Default None (reuse modal). + :param layer: Layer number. Default None (reuse modal). + :param datatype: Datatype number. Default None (reuse modal). + :param x: X-offset. Default None (use modal). + :param y: Y-offset. Default None (use modal). + :param repetition: Repetition. Default None (no repetition). + :raises: InvalidDataError if dimensions are invalid. """ self.radius = radius self.layer = layer @@ -2451,19 +2266,15 @@ class Circle(Record, GeometryMixin): self.x = x self.y = y self.repetition = repetition - self.properties = [] if properties is None else properties - def get_radius(self) -> int: - return verify_modal(self.radius) - - def merge_with_modals(self, modals: Modals) -> None: + def merge_with_modals(self, modals: Modals): adjust_coordinates(self, modals, 'geometry_x', 'geometry_y') adjust_repetition(self, modals) adjust_field(self, 'layer', modals, 'layer') adjust_field(self, 'datatype', modals, 'datatype') adjust_field(self, 'radius', modals, 'circle_radius') - def deduplicate_with_modals(self, modals: Modals) -> None: + def deduplicate_with_modals(self, modals: Modals): dedup_coordinates(self, modals, 'geometry_x', 'geometry_y') dedup_repetition(self, modals) dedup_field(self, 'layer', modals, 'layer') @@ -2471,89 +2282,85 @@ class Circle(Record, GeometryMixin): dedup_field(self, 'radius', modals, 'circle_radius') @staticmethod - def read(stream: IO[bytes], record_id: int) -> 'Circle': + def read(stream: io.BufferedIOBase, record_id: int) -> 'Circle': if record_id != 27: - raise InvalidDataError(f'Invalid record id for Circle: {record_id}') + raise InvalidDataError('Invalid record id for Circle: ' + '{}'.format(record_id)) - z0, z1, has_radius, xx, yy, rr, dd, ll = read_bool_byte(stream) + z0, z1, has_radius, x, y, r, d, l = read_bool_byte(stream) if z0 or z1: raise InvalidDataError('Malformed circle header') - optional: dict[str, Any] = {} - if ll: + optional = {} + if l: optional['layer'] = read_uint(stream) - if dd: + if d: optional['datatype'] = read_uint(stream) if has_radius: optional['radius'] = read_uint(stream) - if xx: + if x: optional['x'] = read_sint(stream) - if yy: + if y: optional['y'] = read_sint(stream) - if rr: + if r: optional['repetition'] = read_repetition(stream) record = Circle(**optional) - logger.debug(f'Record ending at 0x{stream.tell():x}:\n {record}') + logger.debug('Record ending at 0x{:x}:\n {}'.format(stream.tell(), record)) return record - def write(self, stream: IO[bytes]) -> int: - ss = self.radius is not None - xx = self.x is not None - yy = self.y is not None - rr = self.repetition is not None - dd = self.datatype is not None - ll = self.layer is not None + def write(self, stream: io.BufferedIOBase) -> int: + s = self.radius is not None + x = self.x is not None + y = self.y is not None + r = self.repetition is not None + d = self.datatype is not None + l = self.layer is not None size = write_uint(stream, 27) - size += write_bool_byte(stream, (0, 0, ss, xx, yy, rr, dd, ll)) - if ll: - size += write_uint(stream, self.layer) # type: ignore - if dd: - size += write_uint(stream, self.datatype) # type: ignore - if ss: - size += write_uint(stream, self.radius) # type: ignore - if xx: - size += write_sint(stream, self.x) # type: ignore - if yy: - size += write_sint(stream, self.y) # type: ignore - if rr: - size += self.repetition.write(stream) # type: ignore + size += write_bool_byte(stream, (0, 0, s, x, y, r, d, l)) + if l: + size += write_uint(stream, self.layer) + if d: + size += write_uint(stream, self.datatype) + if s: + size += write_uint(stream, self.radius) + if x: + size += write_sint(stream, self.x) + if y: + size += write_sint(stream, self.y) + if r: + size += self.repetition.write(stream) return size -def adjust_repetition(record: HasRepetition, modals: Modals) -> None: +def adjust_repetition(record: Record, modals: Modals): """ Merge the record's repetition entry with the one in the modals - Args: - record: Record to read or modify. - modals: Modals to read or modify. - - Raises: - InvalidDataError: if a `ReuseRepetition` can't be filled - from the modals. + :param record: Record to read or modify. + :param modals: Modals to read or modify. + :raises: InvalidDataError if a ReuseRepetition can't be filled + from the modals. """ if record.repetition is not None: if isinstance(record.repetition, ReuseRepetition): if modals.repetition is None: raise InvalidDataError('Unfillable repetition') - record.repetition = copy.copy(modals.repetition) + else: + record.repetition = copy.copy(modals.repetition) else: modals.repetition = copy.copy(record.repetition) -def adjust_field(record: Record, r_field: str, modals: Modals, m_field: str) -> None: +def adjust_field(record: Record, r_field: str, modals: Modals, m_field: str): """ - Merge `record.r_field` with `modals.m_field` + Merge record.r_field with modals.m_field - Args: - record: `Record` to read or modify. - r_field: Attr of record to access. - modals: `Modals` to read or modify. - m_field: Attr of modals to access. - - Raises: - InvalidDataError: if both fields are `None` + :param record: Record to read or modify. + :param r_field: Attr of record to access. + :param modals: Modals to read or modify. + :param m_field: Attr of modals to access. + :raises: InvalidDataError if a both fields are None """ r = getattr(record, r_field) if r is not None: @@ -2563,55 +2370,51 @@ def adjust_field(record: Record, r_field: str, modals: Modals, m_field: str) -> if m is not None: setattr(record, r_field, copy.copy(m)) else: - raise InvalidDataError(f'Unfillable field: {m_field}') + raise InvalidDataError('Unfillable field: {}'.format(m_field)) -def adjust_coordinates(record: HasXY, modals: Modals, mx_field: str, my_field: str) -> None: +def adjust_coordinates(record: Record, modals: Modals, mx_field: str, my_field: str): """ - Merge `record.x` and `record.y` with `modals.mx_field` and `modals.my_field`, - taking into account the value of `modals.xy_relative`. + Merge record.x and record.y with modals.mx_field and modals.my_field, + taking into account the value of modals.xy_relative. - If `modals.xy_relative` is `True` and the record has non-`None` coordinates, - the modal values are added to the record's coordinates. If `modals.xy_relative` - is `False`, the coordinates are treated the same way as other fields. + If modals.xy_relative is True and the record has non-None coordinates, + the modal values are added to the record's coordinates. If modals.xy_relative + is False, the coordinates are treated the same way as other fields. - Args: - record: `Record` to read or modify. - modals: `Modals` to read or modify. - mx_field: Attr of modals corresponding to `record.x` - my_field: Attr of modals corresponding to `record.y` - - Raises: - InvalidDataError: if both fields are `None` + :param record: Record to read or modify. + :param modals: Modals to read or modify. + :param mx_field: Attr of modals corresponding to record.x + :param my_field: Attr of modals corresponding to record.y + :raises: InvalidDataError if a both fields are None """ if record.x is not None: if modals.xy_relative: record.x += getattr(modals, mx_field) - setattr(modals, mx_field, record.x) + else: + setattr(modals, mx_field, record.x) else: record.x = getattr(modals, mx_field) if record.y is not None: if modals.xy_relative: record.y += getattr(modals, my_field) - setattr(modals, my_field, record.y) + else: + setattr(modals, my_field, record.y) else: record.y = getattr(modals, my_field) # TODO: Clarify the docs on the dedup_* functions -def dedup_repetition(record: HasRepetition, modals: Modals) -> None: +def dedup_repetition(record: Record, modals: Modals): """ Deduplicate the record's repetition entry with the one in the modals. Update the one in the modals if they are different. - Args: - record: `Record` to read or modify. - modals: `Modals` to read or modify. - - Raises: - InvalidDataError: if a `ReuseRepetition` can't be filled - from the modals. + :param record: Record to read or modify. + :param modals: Modals to read or modify. + :raises: InvalidDataError if a ReuseRepetition can't be filled + from the modals. """ if record.repetition is None: return @@ -2627,74 +2430,60 @@ def dedup_repetition(record: HasRepetition, modals: Modals) -> None: modals.repetition = record.repetition -def dedup_field(record: Record, r_field: str, modals: Modals, m_field: str) -> None: +def dedup_field(record: Record, r_field: str, modals: Modals, m_field: str): """ - Deduplicate `record.r_field` using `modals.m_field` - Update the `modals.m_field` if they are different. + Deduplicate record.r_field using modals.m_field + Update the modals.m_field if they are different. - Args: - record: `Record` to read or modify. - r_field: Attr of record to access. - modals: `Modals` to read or modify. - m_field: Attr of modals to access. - - Args: - InvalidDataError: if both fields are `None` + :param record: Record to read or modify. + :param r_field: Attr of record to access. + :param modals: Modals to read or modify. + :param m_field: Attr of modals to access. + :raises: InvalidDataError if a both fields are None """ - rr = getattr(record, r_field) - mm = getattr(modals, m_field) - if rr is not None: - if m_field in ('polygon_point_list', 'path_point_list'): - if _USE_NUMPY: - equal = numpy.array_equal(mm, rr) - else: - equal = (mm is not None) and all(tuple(mmm) == tuple(rrr) for mmm, rrr in zip(mm, rr, strict=True)) - else: - equal = (mm is not None) and mm == rr - - if equal: + r = getattr(record, r_field) + m = getattr(modals, m_field) + if r is not None: + if m is not None and m == r: setattr(record, r_field, None) else: - setattr(modals, m_field, rr) - elif mm is None: + setattr(modals, m_field, r) + elif m is None: raise InvalidDataError('Unfillable field') -def dedup_coordinates(record: HasXY, modals: Modals, mx_field: str, my_field: str) -> None: +def dedup_coordinates(record: Record, modals: Modals, mx_field: str, my_field: str): """ - Deduplicate `record.x` and `record.y` using `modals.mx_field` and `modals.my_field`, - taking into account the value of `modals.xy_relative`. + Deduplicate record.x and record.y using modals.mx_field and modals.my_field, + taking into account the value of modals.xy_relative. - If `modals.xy_relative` is `True` and the record has non-`None` coordinates, - the modal values are subtracted from the record's coordinates. If `modals.xy_relative` - is `False`, the coordinates are treated the same way as other fields. + If modals.xy_relative is True and the record has non-None coordinates, + the modal values are subtracted from the record's coordinates. If modals.xy_relative + is False, the coordinates are treated the same way as other fields. - Args: - record: `Record` to read or modify. - modals: `Modals` to read or modify. - mx_field: Attr of modals corresponding to `record.x` - my_field: Attr of modals corresponding to `record.y` - - Raises: - InvalidDataError: if both fields are `None` + :param record: Record to read or modify. + :param modals: Modals to read or modify. + :param mx_field: Attr of modals corresponding to record.x + :param my_field: Attr of modals corresponding to record.y + :raises: InvalidDataError if a both fields are None """ if record.x is not None: mx = getattr(modals, mx_field) if modals.xy_relative: record.x -= mx - setattr(modals, mx_field, record.x) - elif record.x == mx: - record.x = None else: - setattr(modals, mx_field, record.x) + if record.x == mx: + record.x = None + else: + setattr(modals, mx_field, record.x) if record.y is not None: my = getattr(modals, my_field) if modals.xy_relative: record.y -= my - setattr(modals, my_field, record.y) - elif record.y == my: - record.y = None else: - setattr(modals, my_field, record.y) + if record.y == my: + record.y = None + else: + setattr(modals, my_field, record.y) diff --git a/fatamorgana/test/__init__.py b/fatamorgana/test/__init__.py deleted file mode 100644 index b2eff33..0000000 --- a/fatamorgana/test/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -Tests (run with `python3 -m pytest -rxPXs | tee results.txt`) - - - The test_files_* modules are meant to mimic the test cases used by KLayout, -in order to provide a secondary validation mechanism. -""" diff --git a/fatamorgana/test/build_testfiles.py b/fatamorgana/test/build_testfiles.py deleted file mode 100644 index 50511ba..0000000 --- a/fatamorgana/test/build_testfiles.py +++ /dev/null @@ -1,97 +0,0 @@ -""" -Build files equivalent to the test cases used by KLayout. -""" - -from typing import IO -from collections.abc import Callable -from pathlib import Path - - -from . import ( - test_files_properties, test_files_cblocks, test_files_layernames, - test_files_circles, test_files_ctrapezoids, test_files_trapezoids, - test_files_placements, test_files_paths, test_files_modals, - test_files_polygons, test_files_rectangles, test_files_empty, - test_files_texts, test_files_cells, - ) - - -def build_file(num: str, func: Callable[[IO[bytes]], IO[bytes]]) -> None: - with Path('t' + num + '.oas').open('wb') as ff: - func(ff) - - -def write_all_files() -> None: - build_file('1.1', test_files_empty.write_file_1) - build_file('1.2', test_files_empty.write_file_2) - build_file('1.3', test_files_empty.write_file_3) - build_file('1.4', test_files_empty.write_file_4) - build_file('1.5', test_files_empty.write_file_5) - - build_file('2.1', test_files_cells.write_file_1) - build_file('2.2', test_files_cells.write_file_2) - build_file('2.3', test_files_cells.write_file_3) - build_file('2.4', test_files_cells.write_file_4) - build_file('2.5', test_files_cells.write_file_5) - build_file('2.6', test_files_cells.write_file_6) - build_file('2.7', test_files_cells.write_file_7) - - build_file('3.1', lambda f: test_files_texts.write_file_common(f, 1)) - build_file('3.2', lambda f: test_files_texts.write_file_common(f, 2)) - build_file('3.3', test_files_texts.write_file_3) - build_file('3.4', test_files_texts.write_file_4) - build_file('3.5', lambda f: test_files_texts.write_file_common(f, 5)) - build_file('3.6', test_files_texts.write_file_6) - build_file('3.7', test_files_texts.write_file_7) - build_file('3.8', test_files_texts.write_file_8) - build_file('3.9', test_files_texts.write_file_9) - build_file('3.10', test_files_texts.write_file_10) - build_file('3.11', test_files_texts.write_file_11) - - build_file('4.1', lambda f: test_files_rectangles.write_file_common(f, 1)) - build_file('4.2', lambda f: test_files_rectangles.write_file_common(f, 2)) - - build_file('5.1', lambda f: test_files_polygons.write_file_common(f, 1)) - build_file('5.2', test_files_polygons.write_file_2) - build_file('5.3', lambda f: test_files_polygons.write_file_common(f, 3)) - - build_file('6.1', test_files_paths.write_file_1) - - build_file('7.1', test_files_trapezoids.write_file_1) - - build_file('8.1', test_files_placements.write_file_1) - build_file('8.2', lambda f: test_files_placements.write_file_common(f, 2)) - build_file('8.3', lambda f: test_files_placements.write_file_common(f, 3)) - build_file('8.4', test_files_placements.write_file_4) - build_file('8.5', lambda f: test_files_placements.write_file_common(f, 5)) - build_file('8.6', test_files_placements.write_file_6) - build_file('8.7', lambda f: test_files_placements.write_file_common(f, 7)) - build_file('8.8', test_files_placements.write_file_8) - - build_file('9.1', test_files_ctrapezoids.write_file_1) - build_file('9.2', test_files_ctrapezoids.write_file_2) - - build_file('10.1', test_files_modals.write_file_1) - - build_file('11.1', lambda f: test_files_properties.write_file_common(f, 1)) - build_file('11.2', lambda f: test_files_properties.write_file_common(f, 2)) - build_file('11.3', test_files_properties.write_file_3) - build_file('11.4', lambda f: test_files_properties.write_file_4_6(f, 4)) - build_file('11.5', lambda f: test_files_properties.write_file_common(f, 5)) - build_file('11.6', lambda f: test_files_properties.write_file_4_6(f, 6)) - build_file('11.7', lambda f: test_files_properties.write_file_7_8_9(f, 7)) - build_file('11.8', lambda f: test_files_properties.write_file_7_8_9(f, 8)) - build_file('11.9', lambda f: test_files_properties.write_file_7_8_9(f, 9)) - - build_file('12.1', test_files_circles.write_file_1) - - build_file('13.1', test_files_layernames.write_file_1) - build_file('13.2', test_files_layernames.write_file_2) - build_file('13.3', test_files_layernames.write_file_3) - build_file('13.4', test_files_layernames.write_file_4) - - build_file('14.1', test_files_cblocks.write_file_1) - - -if __name__ == '__main__': - write_all_files() diff --git a/fatamorgana/test/test_files_cblocks.py b/fatamorgana/test/test_files_cblocks.py deleted file mode 100644 index 18c76fa..0000000 --- a/fatamorgana/test/test_files_cblocks.py +++ /dev/null @@ -1,196 +0,0 @@ -# mypy: disable-error-code="union-attr" -from typing import IO -from io import BytesIO - -from numpy.testing import assert_equal - -from .utils import HEADER, FOOTER -from ..basic import write_uint, write_bstring, write_byte -from ..main import OasisLayout - - -def base_tests(layout: OasisLayout) -> None: - assert layout.version.string == '1.0' - assert layout.unit == 1000 - assert layout.validation.checksum_type == 0 - - assert not layout.properties - assert not layout.propnames - assert not layout.xnames - assert not layout.textstrings - assert not layout.cellnames - assert not layout.layers - - assert len(layout.cells) == 1 - assert layout.cells[0].name.string == 'ABCDH' - assert not layout.cells[0].properties - - -def write_file_1(buf: IO[bytes]) -> IO[bytes]: - """ - """ - buf.write(HEADER) - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'ABCDH') # Cell name - - cblock_data = bytes.fromhex(''' - 22 00 b0 02 b2 02 13 - a9 66 60 98 c3 32 89 e5 0e e3 1b 61 91 4a c6 15 - ac 8f 58 3a f8 be f0 8a 5a b0 30 57 5f 64 6d e4 - 4f bd c8 7a 87 ed 81 f8 02 79 a0 88 68 f5 42 b6 - 4e be 80 99 4c 3b 99 35 97 30 4f 14 d7 3c 14 f4 - 52 50 e4 24 e3 0b f6 9b c2 1a 9a 27 18 57 4b 8a - 04 ae 65 3f 12 04 24 36 0b 8b 2c f2 e9 14 16 3d - c6 73 92 4d 64 21 e3 0b 9e cf 9a 15 4f 59 6f 08 - 83 cc 5d c8 f8 91 7b 25 7f ea 4e e6 03 3c 5b a4 - 66 88 01 85 d8 37 b2 fc 64 bd c8 25 5a 79 92 b1 - 99 4b a3 93 65 26 7b 33 bf e6 69 b6 39 7c a9 4b - 40 2e e1 3b 28 a6 79 82 69 41 98 f6 14 ae 60 9b - d7 4c a2 9a 3d 8c 37 f9 6c 03 3f 32 b6 68 2c 64 - 5c cb f3 9a 49 f3 33 e3 0c a6 dd da 29 2f 98 76 - 80 d4 73 df 64 f9 cb b3 58 33 60 36 d3 13 d6 9b - 9c b6 9a 3b 98 5f b2 07 2e 64 dc c9 7c 91 4b 24 - f8 08 cb 6e 45 8d 47 32 1d 12 77 b8 81 4a 59 17 - 68 6a 1f 60 df 28 ac a9 3d 85 b5 5b b6 62 0a ff - 0c 69 90 7b 36 b3 6c 65 d3 9c c9 f4 40 b1 93 a5 - 47 e0 32 7f 8a e6 54 d6 93 6c a2 0f 14 17 c8 03 - 00''') - for byte in cblock_data: - write_byte(buf, byte) - - buf.write(FOOTER) - return buf - - -def test_file_1() -> None: - buf = write_file_1(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - - geometry = layout.cells[0].geometry - assert len(geometry) == 10 - - for ii, gg in enumerate(geometry): - msg = f'Failed on geometry {ii}' - assert gg.x == [110, 900, 1520, -370, 1690, -50, 180, 1540, 970, 2160][ii], msg - assert gg.y == [1270, 890, 2000, 1260, 1420, 850, 860, 750, 1740, 2000][ii], msg - if ii == 0: - assert gg.layer == 0, msg - else: - assert gg.layer == 1, msg - assert gg.datatype == 0, msg - - assert not gg.properties, msg - assert gg.repetition is None, msg - - assert geometry[0].height == 530 - assert geometry[0].width == 540 - assert geometry[1].height == 610 - assert geometry[1].width == 680 - - assert_equal(geometry[2].point_list, [ - [-30, -360], - [480, -50], - [180, 430], - [-630, -20], - ]) - - assert_equal(geometry[3].point_list, [ - [-30, -400], - [450, 40], - [70, -220], - [10, 210], - [740, -20], - [0, 660], - [570, 10], - [50, 500], - [630, 20], - [10, 100], - [-810, 10], - [20, -470], - [-660, 0], - [20, -470], - [-620, 10], - [0, 610], - [610, -10], - [0, -100], - [210, 10], - [40, 820], - [-1340, 60], - [30, -1370], - ]) - - assert_equal(geometry[4].point_list, [ - [40, -760], - [490, -50], - [110, 800], - [-640, 10], - ]) - - assert_equal(geometry[5].point_list, [ - [140, -380], - [340, -10], - [30, -100], - [-320, 20], - [130, -460], - [-480, -20], - [-210, 910], - [370, 40], - ]) - - assert_equal(geometry[6].point_list, [ - [720, -20], - [20, 20], - [690, 0], - [-10, 650], - [-20, 30], - [-90, -10], - [10, 70], - [470, -30], - [20, -120], - [-320, 0], - [40, -790], - [-90, -20], - [-60, 140], - [-1390, 50], - [10, 30], - ]) - - assert_equal(geometry[7].point_list, [ - [150, -830], - [-1320, 40], - [-70, 370], - [310, -30], - [10, 220], - [250, -40], - [40, -220], - [340, 10], - [-20, 290], - [-1070, 20], - [0, 230], - [1380, -60], - ]) - - assert_equal(geometry[8].point_list, [ - [330, 0], - [-10, 480], - [620, -20], - [-10, 330], - [-930, 60], - [0, -850], - ]) - - assert_equal(geometry[9].point_list, [ - [-140, -410], - [10, -140], - [270, 0], - [130, 1030], - [-500, 50], - [10, -330], - [210, -10], - [10, -190], - ]) diff --git a/fatamorgana/test/test_files_cells.py b/fatamorgana/test/test_files_cells.py deleted file mode 100644 index ba286a7..0000000 --- a/fatamorgana/test/test_files_cells.py +++ /dev/null @@ -1,272 +0,0 @@ -# mypy: disable-error-code="union-attr" -from typing import IO -from io import BytesIO - -import pytest - -from .utils import HEADER, FOOTER -from ..basic import write_uint, write_bstring -from ..basic import InvalidRecordError, InvalidDataError -from ..main import OasisLayout - - -def base_tests(layout: OasisLayout) -> None: - assert layout.version.string == '1.0' - assert layout.unit == 1000 - assert layout.validation.checksum_type == 0 - - assert not layout.properties - assert not layout.propnames - assert not layout.xnames - assert not layout.textstrings - assert not layout.propstrings - assert not layout.layers - - -def write_file_1(buf: IO[bytes]) -> IO[bytes]: - """ - Single cell with explicit name 'XYZ' - """ - buf.write(HEADER) - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'XYZ') # Cell name - - buf.write(FOOTER) - return buf - - -def test_file_1() -> None: - buf = write_file_1(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - assert len(layout.cells) == 1 - assert layout.cells[0].name.string == 'XYZ' - assert not layout.cellnames - - -def write_file_2(buf: IO[bytes]) -> IO[bytes]: - """ - Two cellnames ('XYZ', 'ABC') and two cells with name references. - """ - buf.write(HEADER) - - write_uint(buf, 3) # CELLNAME record (implicit id 0) - write_bstring(buf, b'XYZ') - - write_uint(buf, 3) # CELLNAME record (implicit id 1) - write_bstring(buf, b'ABC') - - write_uint(buf, 13) # CELL record (name ref.) - write_uint(buf, 0) # Cell name 0 (XYZ) - - write_uint(buf, 13) # CELL record (name ref.) - write_uint(buf, 1) # Cell name 1 (ABC) - - buf.write(FOOTER) - return buf - - -def test_file_2() -> None: - buf = write_file_2(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - assert len(layout.cellnames) == 2 - assert len(layout.cells) == 2 - assert layout.cellnames[0].nstring.string == 'XYZ' - assert layout.cellnames[1].nstring.string == 'ABC' - assert layout.cells[0].name == 0 - assert layout.cells[1].name == 1 - - -def write_file_3(buf: IO[bytes]) -> IO[bytes]: - """ - Invalid file, contains a mix of explicit and implicit cellnames - """ - buf.write(HEADER) - - write_uint(buf, 4) # CELLNAME record (explicit id) - write_bstring(buf, b'ABC') - write_uint(buf, 1) # id 1 - - write_uint(buf, 3) # CELLNAME record (implicit id 0) -- Expect failure due to mix of explicit/implicit ids - write_bstring(buf, b'XYZ') - - write_uint(buf, 13) # CELL record (name ref.) - write_uint(buf, 0) # Cell name 0 (XYZ) - - write_uint(buf, 13) # CELL record (name ref.) - write_uint(buf, 1) # Cell name 1 (ABC) - - buf.write(FOOTER) - return buf - - -def test_file_3() -> None: - buf = write_file_3(BytesIO()) - - buf.seek(0) - with pytest.raises(InvalidRecordError): - _layout = OasisLayout.read(buf) - - -def write_file_4(buf: IO[bytes]) -> IO[bytes]: - """ - Two cells referencing two names with explicit ids (unsorted) - """ - buf.write(HEADER) - - write_uint(buf, 4) # CELLNAME record (explicit id) - write_bstring(buf, b'ABC') - write_uint(buf, 1) # id 1 - - write_uint(buf, 4) # CELLNAME record (explicit id) - write_bstring(buf, b'XYZ') - write_uint(buf, 0) # id 0 - - write_uint(buf, 13) # CELL record (name ref.) - write_uint(buf, 0) # Cell name 0 (XYZ) - - write_uint(buf, 13) # CELL record (name ref.) - write_uint(buf, 1) # Cell name 1 (ABC) - - buf.write(FOOTER) - return buf - - -def test_file_4() -> None: - buf = write_file_4(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - assert len(layout.cellnames) == 2 - assert len(layout.cells) == 2 - assert layout.cellnames[0].nstring.string == 'XYZ' - assert layout.cellnames[1].nstring.string == 'ABC' - assert layout.cells[0].name == 0 - assert layout.cells[1].name == 1 - - -def write_file_5(buf: IO[bytes]) -> IO[bytes]: - """ - Reference to non-existent cell name. - """ - buf.write(HEADER) - - write_uint(buf, 4) # CELLNAME record (explicit id) - write_bstring(buf, b'ABC') - write_uint(buf, 1) # id 1 - - write_uint(buf, 4) # CELLNAME record (explicit id) - write_bstring(buf, b'XYZ') - write_uint(buf, 0) # id 0 - - write_uint(buf, 13) # CELL record (name ref.) - write_uint(buf, 0) # Cell name 0 (XYZ) - - write_uint(buf, 13) # CELL record (name ref.) - write_uint(buf, 2) # Cell name 2 -- Reference to non-existent CELLNAME!!! - - buf.write(FOOTER) - return buf - - -def test_file_5() -> None: - buf = write_file_5(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - assert len(layout.cellnames) == 2 - assert len(layout.cells) == 2 - assert layout.cellnames[0].nstring.string == 'XYZ' - assert layout.cellnames[1].nstring.string == 'ABC' - assert layout.cells[0].name == 0 - assert layout.cells[1].name == 2 - - #TODO add optional error checking for this case - - -def write_file_6(buf: IO[bytes]) -> IO[bytes]: - """ - Cellname with invalid n-string. - """ - buf.write(HEADER) - - write_uint(buf, 4) # CELLNAME record (explicit id) - write_bstring(buf, b'ABC') - write_uint(buf, 1) # id 1 - - write_uint(buf, 4) # CELLNAME record (explicit id) - write_bstring(buf, b' XYZ') - write_uint(buf, 0) # id 0 - - write_uint(buf, 13) # CELL record (name ref.) - write_uint(buf, 0) # Cell name 0 (XYZ) - - write_uint(buf, 13) # CELL record (name ref.) - write_uint(buf, 1) # Cell name 1 - - buf.write(FOOTER) - return buf - - -def test_file_6() -> None: - buf = write_file_6(BytesIO()) - - buf.seek(0) - - with pytest.raises(InvalidDataError): - _layout = OasisLayout.read(buf) - - #base_tests(layout) - #assert len(layout.cellnames) == 2 - #assert len(layout.cells) == 2 - #assert layout.cellnames[0].nstring.string == ' XYZ' - #assert layout.cellnames[1].nstring.string == 'ABC' - #assert layout.cells[0].name == 0 - #assert layout.cells[1].name == 1 - - -def write_file_7(buf: IO[bytes]) -> IO[bytes]: - """ - Unused cellname. - """ - buf.write(HEADER) - - write_uint(buf, 4) # CELLNAME record (explicit id) - write_bstring(buf, b'ABC') - write_uint(buf, 1) # id 1 - - write_uint(buf, 4) # CELLNAME record (explicit id) - write_bstring(buf, b'XYZ') - write_uint(buf, 0) # id 0 - - write_uint(buf, 13) # CELL record (name ref.) - write_uint(buf, 0) # Cell name 0 (XYZ) - - buf.write(FOOTER) - return buf - - -def test_file_7() -> None: - buf = write_file_7(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - assert len(layout.cellnames) == 2 - assert len(layout.cells) == 1 - assert layout.cellnames[0].nstring.string == 'XYZ' - assert layout.cellnames[1].nstring.string == 'ABC' - assert layout.cells[0].name == 0 diff --git a/fatamorgana/test/test_files_circles.py b/fatamorgana/test/test_files_circles.py deleted file mode 100644 index 39e54a0..0000000 --- a/fatamorgana/test/test_files_circles.py +++ /dev/null @@ -1,109 +0,0 @@ -# mypy: disable-error-code="union-attr" -from typing import IO -from io import BytesIO - -from .utils import HEADER, FOOTER -from ..basic import write_uint, write_sint, write_bstring, write_byte -from ..main import OasisLayout - - -def base_tests(layout: OasisLayout) -> None: - assert layout.version.string == '1.0' - assert layout.unit == 1000 - assert layout.validation.checksum_type == 0 - - assert not layout.properties - assert not layout.propnames - assert not layout.xnames - assert not layout.textstrings - assert not layout.cellnames - assert not layout.layers - - assert len(layout.cells) == 1 - assert layout.cells[0].name.string == 'A' - assert not layout.cells[0].properties - - -def write_file_1(buf: IO[bytes]) -> IO[bytes]: - """ - """ - buf.write(HEADER) - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'A') # Cell name - - write_uint(buf, 27) # CIRCLE record - write_byte(buf, 0b0011_1011) # 00rX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 150) # radius - write_sint(buf, -100) # geometry-x (absolute) - write_sint(buf, 200) # geometry-y (absolute) - - write_uint(buf, 16) # XYRELATIVE record - - write_uint(buf, 27) # CIRCLE record - write_byte(buf, 0b0000_1000) # 00rX_YRDL - write_sint(buf, 400) # geometry-y (relative) - - write_uint(buf, 27) # CIRCLE record - write_byte(buf, 0b0010_1000) # 00rX_YRDL - write_uint(buf, 0) # radius - write_sint(buf, 400) # geometry-y (relative) - - write_uint(buf, 27) # CIRCLE record - write_byte(buf, 0b0010_1000) # 00rX_YRDL - write_uint(buf, 1) # radius - write_sint(buf, 400) # geometry-y (relative) - - write_uint(buf, 27) # CIRCLE record - write_byte(buf, 0b0010_1000) # 00rX_YRDL - write_uint(buf, 6) # radius - write_sint(buf, 400) # geometry-y (relative) - - write_uint(buf, 27) # CIRCLE record - write_byte(buf, 0b0010_1000) # 00rX_YRDL - write_uint(buf, 20) # radius - write_sint(buf, 400) # geometry-y (relative) - - write_uint(buf, 27) # CIRCLE record - write_byte(buf, 0b0010_1100) # 00rX_YRDL - write_uint(buf, 100) # radius - write_sint(buf, 400) # geometry-y (relative) - write_uint(buf, 1) # repetition (3x4 matrix) - write_uint(buf, 1) # (repetition) x-dimension - write_uint(buf, 2) # (repetition) y-dimension - write_uint(buf, 400) # (repetition) x-spacing - write_uint(buf, 300) # (repetition) y-spacing - - buf.write(FOOTER) - return buf - - -def test_file_1() -> None: - buf = write_file_1(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - - geometry = layout.cells[0].geometry - assert len(geometry) == 7 - for ii, gg in enumerate(geometry): - msg = f'Failed on circle {ii}' - assert gg.x == -100, msg - assert gg.y == 200 + 400 * ii, msg - - assert gg.layer == 1, msg - assert gg.datatype == 2, msg - assert not gg.properties, msg - assert gg.radius == [150, 150, 0, 1, 6, 20, 100][ii], msg - - if ii != 6: - assert gg.repetition is None, msg - - assert geometry[6].repetition.a_count == 3, msg - assert geometry[6].repetition.b_count == 4, msg - assert geometry[6].repetition.a_vector == [400, 0], msg - assert geometry[6].repetition.b_vector == [0, 300], msg diff --git a/fatamorgana/test/test_files_ctrapezoids.py b/fatamorgana/test/test_files_ctrapezoids.py deleted file mode 100644 index 37fc80f..0000000 --- a/fatamorgana/test/test_files_ctrapezoids.py +++ /dev/null @@ -1,239 +0,0 @@ -# mypy: disable-error-code="union-attr" -from typing import IO -from io import BytesIO - -from .utils import HEADER, FOOTER -from ..basic import write_uint, write_sint, write_bstring, write_byte -from ..main import OasisLayout - - -def base_tests(layout: OasisLayout) -> None: - assert layout.version.string == '1.0' - assert layout.unit == 1000 - assert layout.validation.checksum_type == 0 - - assert not layout.properties - assert not layout.propnames - assert not layout.xnames - assert not layout.textstrings - assert not layout.cellnames - assert not layout.layers - - -def write_file_1(buf: IO[bytes]) -> IO[bytes]: - """ - """ - buf.write(HEADER) - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'A') # Cell name - - write_uint(buf, 26) # CTRAPEZOID record - write_byte(buf, 0b1111_1011) # TWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 24) # ctrapezoid type - write_uint(buf, 100) # width - write_uint(buf, 200) # height - write_sint(buf, -100) # geometry-x (absolute) - write_sint(buf, 200) # geometry-y (absolute) - - write_uint(buf, 16) # XYRELATIVE record - - write_uint(buf, 26) # CTRAPEZOID record - write_byte(buf, 0b0000_1000) # TWHX_YRDL - write_sint(buf, 400) # geometry-y (relative) - - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0000_0011) # SWHX_YRDL - write_uint(buf, 2) # layer - write_uint(buf, 3) # datatype - - h = [250, 100] - v = [100, 250] - - wh = [h] * 8 + [v] * 8 + [h] * 6 + [v] * 2 + [h] * 2 - - wh_en = ([0b11] * 16 - + [0b10] * 4 - + [0b01] * 2 - + [0b10] * 2 - + [0b11, 0b10] - ) - - for t, (x, x_en) in enumerate(zip(wh, wh_en, strict=True)): - write_uint(buf, 26) # CTRAPEZOID record - write_byte(buf, 0b1000_1011 | (x_en << 5)) # TWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, t) # ctrapezoid type - if x_en & 0b10: - write_uint(buf, x[0]) # width - if x_en & 0b01: - write_uint(buf, x[1]) # height - write_sint(buf, 400) # geometry-y (relative) - - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0000_0011) # SWHX_YRDL - write_uint(buf, 2) # layer - write_uint(buf, 3) # datatype - - write_uint(buf, 26) # CTRAPEZOID record - write_byte(buf, 0b0000_1100) # TWHX_YRDL - write_sint(buf, 400) # geometry-y (relative) - write_uint(buf, 1) # repetition (3x4 matrix) - write_uint(buf, 1) # (repetition) x-dimension - write_uint(buf, 2) # (repetition) y-dimension - write_uint(buf, 400) # (repetition) x-spacing - write_uint(buf, 300) # (repetition) y-spacing - - buf.write(FOOTER) - return buf - - -def test_file_1() -> None: - buf = write_file_1(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - - assert len(layout.cells) == 1 - assert layout.cells[0].name.string == 'A' - assert not layout.cells[0].properties - - geometry = layout.cells[0].geometry - assert len(geometry) == 3 + 26 * 2 + 1 - - for ii, gg in enumerate(geometry): - msg = f'Failed on shape {ii}' - assert gg.x == -100, msg - assert gg.y == 200 + 400 * ((ii + 1) // 2), msg - - if ii < 2 or (3 <= ii < 55 and ii % 2 == 1): - assert gg.layer == 1, msg - assert gg.datatype == 2, msg - else: - assert gg.layer == 2, msg - assert gg.datatype == 3, msg - - if ii < 3: - assert gg.width == 100, msg - assert gg.height == 200, msg - - assert not gg.properties, msg - - if 3 <= ii < 55: - ct_type = (ii - 3) // 2 - is_ctrapz = ii % 2 == 1 - if is_ctrapz: - assert gg.ctrapezoid_type == ct_type, msg - if ct_type in range(16, 20): - assert gg.height == [250, None][is_ctrapz], msg - elif ct_type in (20, 21): - assert gg.width == [250, None][is_ctrapz], msg - elif ct_type in range(22, 24) or ct_type == 25: - assert gg.height == [100, None][is_ctrapz], msg - elif ct_type < 8 or 16 <= ct_type < 25 or ct_type >= 26: - assert gg.width == 250, msg - assert gg.height == 100, msg - else: - assert gg.width == 100, msg - assert gg.height == 250, msg - elif ii < 3 and ii % 2: - assert gg.ctrapezoid_type == 24, msg - elif ii == 55: - assert gg.ctrapezoid_type == 25, msg - - assert geometry[55].repetition.a_count == 3 - assert geometry[55].repetition.b_count == 4 - assert geometry[55].repetition.a_vector == [400, 0] - assert geometry[55].repetition.b_vector == [0, 300] - - -def write_file_2(buf: IO[bytes]) -> IO[bytes]: - """ - """ - buf.write(HEADER) - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'A') # Cell name - - # Shouldn't access (undefined) height modal, despite not having a height. - write_uint(buf, 26) # CTRAPEZOID record - write_byte(buf, 0b1101_1011) # TWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 16) # ctrapezoid type - write_uint(buf, 200) # width - write_sint(buf, -100) # geometry-x (absolute) - write_sint(buf, 200) # geometry-y (absolute) - - write_uint(buf, 16) # XYRELATIVE record - - write_uint(buf, 26) # CTRAPEZOID record - write_byte(buf, 0b0000_1000) # TWHX_YRDL - write_sint(buf, 400) # geometry-y (relative) - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'B') # Cell name - - # Shouldn't access (undefined) width modal, despite not having a width. - write_uint(buf, 26) # CTRAPEZOID record - write_byte(buf, 0b1011_1011) # TWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 20) # ctrapezoid type - write_uint(buf, 200) # height - write_sint(buf, -100) # geometry-x (absolute) - write_sint(buf, 200) # geometry-y (absolute) - - write_uint(buf, 16) # XYRELATIVE record - - write_uint(buf, 26) # CTRAPEZOID record - write_byte(buf, 0b0000_1000) # TWHX_YRDL - write_sint(buf, 400) # geometry-y (relative) - - buf.write(FOOTER) - return buf - - -def test_file_2() -> None: - buf = write_file_2(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - - assert len(layout.cells) == 2 - assert layout.cells[0].name.string == 'A' - assert layout.cells[1].name.string == 'B' - assert not layout.cells[0].properties - assert not layout.cells[1].properties - - for ii, cc in enumerate(layout.cells): - for jj, gg in enumerate(cc.geometry): - msg = f'Fail in cell {ii}, ctrapezoid {jj}' - assert not gg.properties, msg - assert gg.layer == 1, msg - assert gg.datatype == 2, msg - assert gg.x == -100, msg - - geometry = layout.cells[0].geometry - assert geometry[0].width == 200 - assert geometry[1].width == 200 - assert geometry[0].ctrapezoid_type == 16 - assert geometry[1].ctrapezoid_type == 16 - assert geometry[0].y == 200 - assert geometry[1].y == 600 - - geometry = layout.cells[1].geometry - assert geometry[0].height == 200 - assert geometry[1].height == 200 - assert geometry[0].ctrapezoid_type == 20 - assert geometry[1].ctrapezoid_type == 20 - assert geometry[0].y == 200 - assert geometry[1].y == 600 - diff --git a/fatamorgana/test/test_files_empty.py b/fatamorgana/test/test_files_empty.py deleted file mode 100644 index a0fdbee..0000000 --- a/fatamorgana/test/test_files_empty.py +++ /dev/null @@ -1,179 +0,0 @@ -from typing import IO -from io import BytesIO -import struct - -from .utils import MAGIC_BYTES, FOOTER -from ..basic import write_uint, write_bstring -from ..main import OasisLayout - - -def base_tests(layout: OasisLayout) -> None: - assert layout.version.string == '1.0' - assert layout.validation.checksum_type == 0 - - assert not layout.properties - assert not layout.cells - assert not layout.cellnames - assert not layout.propnames - assert not layout.xnames - assert not layout.textstrings - assert not layout.propstrings - assert not layout.layers - - -def write_file_1(buf: IO[bytes]) -> IO[bytes]: - """ - File contains one PAD record. - 1000 units/micron - Offset table inside START. - """ - buf.write(MAGIC_BYTES) - - write_uint(buf, 1) # START record - write_bstring(buf, b'1.0') # version - write_uint(buf, 0) # dbu real type: uint - write_uint(buf, 1000) # dbu value: 1000 per micron - write_uint(buf, 0) # offset table is present here - for _ in range(6): - write_uint(buf, 0) # offset table (0: not strict) - write_uint(buf, 0) # offset table (0: no entry present) - - write_uint(buf, 0) # PAD record - - buf.write(FOOTER) - return buf - - -def test_file_1() -> None: - buf = write_file_1(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - assert layout.unit == 1000 - - -def write_file_2(buf: IO[bytes]) -> IO[bytes]: - """ - File contains no records. - 1/2 unit/micron - Offset table inside START. - """ - buf.write(MAGIC_BYTES) - - write_uint(buf, 1) # START record - write_bstring(buf, b'1.0') # version - write_uint(buf, 2) # dbu real type: fraction 1/x - write_uint(buf, 2) # dbu value: 1/2 per micron - write_uint(buf, 0) # offset table is present here - for _ in range(6): - write_uint(buf, 0) # offset table (0: not strict) - write_uint(buf, 0) # offset table (0: no entry present) - - buf.write(FOOTER) - return buf - - -def test_file_2() -> None: - buf = write_file_2(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - assert layout.unit == 0.5 - - -def write_file_3(buf: IO[bytes]) -> IO[bytes]: - """ - File contains no records. - 10/4 unit/micron - Offset table inside START. - """ - buf.write(MAGIC_BYTES) - - write_uint(buf, 1) # START record - write_bstring(buf, b'1.0') # version - write_uint(buf, 4) # dbu real type: fraction a/b - write_uint(buf, 10) # dbu value a - write_uint(buf, 4) # dbu value b: 10/4 per micron - write_uint(buf, 0) # offset table is present here - for _ in range(6): - write_uint(buf, 0) # offset table (0: not strict) - write_uint(buf, 0) # offset table (0: no entry present) - - buf.write(FOOTER) - return buf - - -def test_file_3() -> None: - buf = write_file_3(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - assert layout.unit == 10 / 4 - - -def write_file_4(buf: IO[bytes]) -> IO[bytes]: - """ - File contains no records. - 12.5 unit/micron (float32) - Offset table inside START. - """ - buf.write(MAGIC_BYTES) - - write_uint(buf, 1) # START record - write_bstring(buf, b'1.0') # version - write_uint(buf, 6) # dbu real type: float32 - buf.write(struct.pack(" None: - buf = write_file_4(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - assert layout.unit == 12.5 - - -def write_file_5(buf: IO[bytes]) -> IO[bytes]: - """ - File contains no records. - 12.5 unit/micron (float64) - Offset table inside START. - """ - buf.write(MAGIC_BYTES) - - write_uint(buf, 1) # START record - write_bstring(buf, b'1.0') # version - write_uint(buf, 7) # dbu real type: float64 - buf.write(struct.pack(" None: - buf = write_file_5(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - assert layout.unit == 12.5 diff --git a/fatamorgana/test/test_files_layernames.py b/fatamorgana/test/test_files_layernames.py deleted file mode 100644 index 72e9af6..0000000 --- a/fatamorgana/test/test_files_layernames.py +++ /dev/null @@ -1,335 +0,0 @@ -from typing import IO -from collections.abc import Sequence - -from io import BytesIO - -from .utils import HEADER, FOOTER -from ..basic import write_uint, write_sint, write_bstring, write_byte -from ..main import OasisLayout - - -LAYERS = [ - (1, 2), (1, 5), (1, 6), (1, 8), - (5, 2), (5, 5), (5, 6), (5, 8), - (6, 2), (6, 5), (6, 6), (6, 8), - (7, 2), (7, 5), (7, 6), (7, 8), - ] - -def base_tests(layout: OasisLayout) -> None: - assert layout.version.string == '1.0' - assert layout.unit == 1000 - assert layout.validation.checksum_type == 0 - - assert not layout.properties - assert not layout.propnames - assert not layout.xnames - assert not layout.textstrings - assert not layout.cellnames - - assert len(layout.cells) == 1 - assert layout.cells[0].name.string == 'A' # type: ignore - assert not layout.cells[0].properties - - -def write_names_geom(buf: IO[bytes], short: bool = False) -> IO[bytes]: - write_uint(buf, 11) # LAYERNAME record (geometry) - write_bstring(buf, b'AA') # name - write_uint(buf, 0) # all layers - write_uint(buf, 0) # all datatypes - - write_uint(buf, 11) # LAYERNAME record (geometry) - write_bstring(buf, b'L5A') # name - write_uint(buf, 1) # layer <=5 - write_uint(buf, 5) # (...) - write_uint(buf, 0) # all datatypes - - write_uint(buf, 11) # LAYERNAME record (geometry) - write_bstring(buf, b'H5A') # name - write_uint(buf, 2) # layer >=5 - write_uint(buf, 5) # (...) - write_uint(buf, 0) # all datatypes - - write_uint(buf, 11) # LAYERNAME record (geometry) - write_bstring(buf, b'E5A') # name - write_uint(buf, 3) # layer ==5 - write_uint(buf, 5) # (...) - write_uint(buf, 0) # all datatypes - - write_uint(buf, 11) # LAYERNAME record (geometry) - write_bstring(buf, b'I56A') # name - write_uint(buf, 4) # layer 5 to 6 - write_uint(buf, 5) # (...) - write_uint(buf, 6) # (...) - write_uint(buf, 0) # all datatypes - - if short: - return buf - - write_uint(buf, 11) # LAYERNAME record (geometry) - write_bstring(buf, b'E5L4') # name - write_uint(buf, 3) # layer ==5 - write_uint(buf, 5) # (...) - write_uint(buf, 1) # datatype <=4 - write_uint(buf, 4) # (...) - - write_uint(buf, 11) # LAYERNAME record (geometry) - write_bstring(buf, b'E5H4') # name - write_uint(buf, 3) # layer ==5 - write_uint(buf, 5) # (...) - write_uint(buf, 2) # datatype >=4 - write_uint(buf, 4) # (...) - - write_uint(buf, 11) # LAYERNAME record (geometry) - write_bstring(buf, b'E5E4') # name - write_uint(buf, 3) # layer ==5 - write_uint(buf, 5) # (...) - write_uint(buf, 3) # datatype ==4 - write_uint(buf, 4) # (...) - - write_uint(buf, 11) # LAYERNAME record (geometry) - write_bstring(buf, b'E5I47') # name - write_uint(buf, 3) # layer ==5 - write_uint(buf, 5) # (...) - write_uint(buf, 4) # datatype 4 to 7 - write_uint(buf, 4) # (...) - write_uint(buf, 7) # (...) - - return buf - - -def write_names_text(buf: IO[bytes], prefix: bytes = b'') -> IO[bytes]: - write_uint(buf, 12) # LAYERNAME record (geometry) - write_bstring(buf, prefix + b'AA') # name - write_uint(buf, 0) # all layers - write_uint(buf, 0) # all datatypes - - write_uint(buf, 12) # LAYERNAME record (geometry) - write_bstring(buf, prefix + b'L5A') # name - write_uint(buf, 1) # layer <=5 - write_uint(buf, 5) # (...) - write_uint(buf, 0) # all datatypes - - write_uint(buf, 12) # LAYERNAME record (geometry) - write_bstring(buf, prefix + b'H5A') # name - write_uint(buf, 2) # layer >=5 - write_uint(buf, 5) # (...) - write_uint(buf, 0) # all datatypes - - write_uint(buf, 12) # LAYERNAME record (geometry) - write_bstring(buf, prefix + b'E5A') # name - write_uint(buf, 3) # layer ==5 - write_uint(buf, 5) # (...) - write_uint(buf, 0) # all datatypes - - write_uint(buf, 12) # LAYERNAME record (geometry) - write_bstring(buf, prefix + b'I56A') # name - write_uint(buf, 4) # layer 5 to 6 - write_uint(buf, 5) # (...) - write_uint(buf, 6) # (...) - write_uint(buf, 0) # all datatypes - return buf - -def write_geom(buf: IO[bytes]) -> IO[bytes]: - for ll, dt in LAYERS: - write_uint(buf, 27) # CIRCLE record - write_byte(buf, 0b0011_1011) # 00rX_YRDL - write_uint(buf, ll) # layer - write_uint(buf, dt) # datatype - write_uint(buf, 150) # radius - write_sint(buf, ll * 1000) # geometry-x (absolute) - write_sint(buf, dt * 1000) # geometry-y (absolute) - return buf - - -def write_text(buf: IO[bytes]) -> IO[bytes]: - for ll, dt in LAYERS: - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0101_1011) # 0CNX_YRTL - write_bstring(buf, b'A') # text-string - write_uint(buf, ll) # text-layer - write_uint(buf, dt) # text-datatype - write_sint(buf, ll * 1000) # geometry-x - write_sint(buf, dt * 1000) # geometry-y - return buf - - -def name_test(layers: Sequence, is_textlayer: bool) -> None: - for ii, nn in enumerate(layers): - msg = f'Fail on layername {ii}' - assert is_textlayer == nn.is_textlayer, msg - - assert nn.nstring.string == ['AA', 'L5A', 'H5A', 'E5A', 'I56A', - 'E5L4', 'E5H4', 'E5E4', 'E5I47'][ii], msg - assert nn.layer_interval[0] == [None, None, 5, 5, 5, 5, 5, 5, 5][ii], msg - assert nn.layer_interval[1] == [None, 5, None, 5, 6, 5, 5, 5, 5][ii], msg - assert nn.type_interval[0] == [None, None, None, None, None, None, 4, 4, 4][ii], msg - assert nn.type_interval[1] == [None, None, None, None, None, 4, None, 4, 7][ii], msg - - -def name_test_text(layers: Sequence) -> None: - for ii, nn in enumerate(layers): - msg = f'Fail on layername {ii}' - assert nn.is_textlayer, msg - - assert nn.nstring.string == ['TAA', 'TL5A', 'TH5A', 'TE5A', 'TI56A'][ii], msg - assert nn.layer_interval[0] == [None, None, 5, 5, 5][ii], msg - assert nn.layer_interval[1] == [None, 5, None, 5, 6][ii], msg - assert nn.type_interval[0] == [None, None, None, None, None][ii], msg - assert nn.type_interval[1] == [None, None, None, None, None][ii], msg - - -def elem_test_geom(geometry: Sequence) -> None: - for ii, gg in enumerate(geometry): - msg = f'Failed on circle ({ii})' - assert gg.x == 1000 * LAYERS[ii][0], msg - assert gg.y == 1000 * LAYERS[ii][1], msg - assert gg.radius == 150, msg - - assert gg.layer == LAYERS[ii][0], msg - assert gg.datatype == LAYERS[ii][1], msg - - assert gg.repetition is None, msg - assert not gg.properties, msg - - -def elem_test_text(geometry: Sequence) -> None: - for ii, gg in enumerate(geometry): - msg = f'Failed on text ({ii})' - assert gg.x == 1000 * LAYERS[ii][0], msg - assert gg.y == 1000 * LAYERS[ii][1], msg - assert gg.string.string == 'A', msg - - assert gg.layer == LAYERS[ii][0], msg - assert gg.datatype == LAYERS[ii][1], msg - - assert gg.repetition is None, msg - assert not gg.properties, msg - - -def write_file_1(buf: IO[bytes]) -> IO[bytes]: - """ - """ - buf.write(HEADER) - write_names_geom(buf) - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'A') # Cell name - - write_geom(buf) - buf.write(FOOTER) - return buf - - -def test_file_1() -> None: - buf = write_file_1(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - - geometry = layout.cells[0].geometry - assert len(geometry) == len(LAYERS) - elem_test_geom(geometry) - - assert len(layout.layers) == 9 - name_test(layout.layers, is_textlayer=False) - - -def write_file_2(buf: IO[bytes]) -> IO[bytes]: - """ - """ - buf.write(HEADER) - write_names_text(buf) - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'A') # Cell name - - write_text(buf) - buf.write(FOOTER) - return buf - - -def test_file_2() -> None: - buf = write_file_2(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - - geometry = layout.cells[0].geometry - assert len(geometry) == len(LAYERS) - elem_test_text(geometry) - - assert len(layout.layers) == 5 - name_test(layout.layers, is_textlayer=True) - - -def write_file_3(buf: IO[bytes]) -> IO[bytes]: - """ - """ - buf.write(HEADER) - write_names_text(buf, prefix=b'T') - write_names_geom(buf, short=True) - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'A') # Cell name - - write_text(buf) - write_geom(buf) - buf.write(FOOTER) - return buf - - -def write_file_4(buf: IO[bytes]) -> IO[bytes]: - """ - """ - buf.write(HEADER) - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'A') # Cell name - - write_text(buf) - write_geom(buf) - - write_names_text(buf, prefix=b'T') - write_names_geom(buf, short=True) - buf.write(FOOTER) - return buf - - -def test_file_3() -> None: - buf = write_file_3(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - - geometry = layout.cells[0].geometry - assert len(geometry) == 2 * len(LAYERS) - elem_test_text(geometry[:len(LAYERS)]) - elem_test_geom(geometry[len(LAYERS):]) - - assert len(layout.layers) == 2 * 5 - name_test_text(layout.layers[:5]) - name_test(layout.layers[5:], is_textlayer=False) - - -def test_file_4() -> None: - buf = write_file_4(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - - geometry = layout.cells[0].geometry - assert len(geometry) == 2 * len(LAYERS) - elem_test_text(geometry[:len(LAYERS)]) - elem_test_geom(geometry[len(LAYERS):]) - - assert len(layout.layers) == 2 * 5 - name_test_text(layout.layers[:5]) - name_test(layout.layers[5:], is_textlayer=False) diff --git a/fatamorgana/test/test_files_modals.py b/fatamorgana/test/test_files_modals.py deleted file mode 100644 index fddc576..0000000 --- a/fatamorgana/test/test_files_modals.py +++ /dev/null @@ -1,252 +0,0 @@ -# mypy: disable-error-code="union-attr" -from typing import IO -from io import BytesIO - -from .utils import HEADER, FOOTER -from ..basic import write_uint, write_sint, write_bstring, write_byte -from ..main import OasisLayout - - -def base_tests(layout: OasisLayout) -> None: - assert layout.version.string == '1.0' - assert layout.unit == 1000 - assert layout.validation.checksum_type == 0 - - assert not layout.properties - assert not layout.propnames - assert not layout.xnames - assert not layout.textstrings - assert not layout.cellnames - assert not layout.layers - - -def write_file_1(buf: IO[bytes]) -> IO[bytes]: - """ - """ - buf.write(HEADER) - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'A') # Cell name - - # RECTANGLE 0 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0110_0011) # SWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 10) # width - write_uint(buf, 20) # height - - # TEXT 1 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0100_0011) # 0CNX_YRTL - write_bstring(buf, b'A') # text string - write_uint(buf, 2) # layer - write_uint(buf, 1) # datatype - - # RECTANGLE 2 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0001_1000) # SWHX_YRDL - write_sint(buf, 100) # geometry-x (absolute) - write_sint(buf, -100) # geometry-y (absolute) - - # TEXT 3 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0001_1000) # 0CNX_YRTL - write_sint(buf, 100) # text-x (absolute) - write_sint(buf, -100) # text-y (absolute) - - # RECTANGLE 4 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0001_1000) # SWHX_YRDL - write_sint(buf, 200) # geometry-x (absolute) - write_sint(buf, -200) # geometry-y (absolute) - - # TEXT 5 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0001_1000) # 0CNX_YRTL - write_sint(buf, 200) # text-x (absolute) - write_sint(buf, -200) # text-y (absolute) - - write_uint(buf, 16) # XYRELATIVE record - - # RECTANGLE 6 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0001_1000) # SWHX_YRDL - write_sint(buf, 100) # geometry-x (relative) - write_sint(buf, -100) # geometry-y (relative) - - # TEXT 7 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0001_1000) # 0CNX_YRTL - write_sint(buf, 100) # text-x (relative) - write_sint(buf, -100) # text-y (relative) - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'B') # Cell name - - # RECTANGLE 0 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0110_0011) # SWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 20) # width - write_uint(buf, 10) # height - - # TEXT 1 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0100_0011) # 0CNX_YRTL - write_bstring(buf, b'B') # text string - write_uint(buf, 2) # layer - write_uint(buf, 1) # datatype - - # RECTANGLE 2 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0001_1000) # SWHX_YRDL - write_sint(buf, 100) # geometry-x (absolute) - write_sint(buf, 100) # geometry-y (absolute) - - # TEXT 3 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0001_1000) # 0CNX_YRTL - write_sint(buf, 100) # text-x (absolute) - write_sint(buf, 100) # text-y (absolute) - - # RECTANGLE 4 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0001_1000) # SWHX_YRDL - write_sint(buf, 200) # geometry-x (absolute) - write_sint(buf, 200) # geometry-y (absolute) - - # TEXT 5 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0001_1000) # 0CNX_YRTL - write_sint(buf, 200) # text-x (absolute) - write_sint(buf, 200) # text-y (absolute) - - write_uint(buf, 16) # XYRELATIVE record - - # RECTANGLE 6 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0001_1000) # SWHX_YRDL - write_sint(buf, 100) # geometry-x (relative) - write_sint(buf, 100) # geometry-y (relative) - - # TEXT 7 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0001_1000) # 0CNX_YRTL - write_sint(buf, 100) # text-x (relative) - write_sint(buf, 100) # text-y (relative) - - # PLACEMENT 0 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b1000_0000) # CNXY_RAAF - write_bstring(buf, b'A') # Cell reference - - # PLACEMENT 1 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0011_0000) # CNXY_RAAF - write_sint(buf, 50) # placement-x (relative) - write_sint(buf, 50) # placement-y (relative) - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'TOP') # Cell name - - # PLACEMENT 0 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b1000_0000) # CNXY_RAAF - write_bstring(buf, b'B') # Cell reference - - # RECTANGLE 0 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0110_0011) # SWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 50) # width - write_uint(buf, 5) # height - - # TEXT 1 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0100_0011) # 0CNX_YRTL - write_bstring(buf, b'TOP') # text string - write_uint(buf, 2) # layer - write_uint(buf, 1) # datatype - - buf.write(FOOTER) - return buf - - -def test_file_1() -> None: - buf = write_file_1(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - - assert len(layout.cells) == 3 - assert layout.cells[0].name.string == 'A' - assert layout.cells[1].name.string == 'B' - assert layout.cells[2].name.string == 'TOP' - assert not layout.cells[0].properties - assert not layout.cells[1].properties - assert not layout.cells[2].properties - - geometry = layout.cells[0].geometry - assert len(geometry) == 8 - for ii, gg in enumerate(geometry): - msg = f'Failed on geometry {ii} in cell A' - - if ii % 2 == 0: - assert gg.width == 10, msg - assert gg.height == 20, msg - assert gg.layer == 1, msg - assert gg.datatype == 2, msg - else: - assert gg.string.string == 'A', msg - assert gg.layer == 2, msg - assert gg.datatype == 1, msg - assert not gg.properties, msg - assert gg.x == (ii // 2) * 100, msg - assert gg.y == (ii // 2) * -100, msg - - geometry = layout.cells[1].geometry - assert len(geometry) == 8 - for ii, gg in enumerate(geometry): - msg = f'Failed on geometry {ii} in cell B' - - if ii % 2 == 0: - assert gg.width == 20, msg - assert gg.height == 10, msg - assert gg.layer == 1, msg - assert gg.datatype == 2, msg - else: - assert gg.string.string == 'B', msg - assert gg.layer == 2, msg - assert gg.datatype == 1, msg - assert not gg.properties, msg - assert gg.x == (ii // 2) * 100, msg - assert gg.y == (ii // 2) * 100, msg - - assert layout.cells[1].placements[0].name.string == 'A' - assert layout.cells[1].placements[1].name.string == 'A' - assert layout.cells[1].placements[0].x == 0 - assert layout.cells[1].placements[0].y == 0 - assert layout.cells[1].placements[1].x == 50 - assert layout.cells[1].placements[1].y == 50 - - assert layout.cells[2].placements[0].name.string == 'B' - assert layout.cells[2].placements[0].x == 0 - assert layout.cells[2].placements[0].y == 0 - - assert layout.cells[2].geometry[0].layer == 1 - assert layout.cells[2].geometry[0].datatype == 2 - assert layout.cells[2].geometry[0].width == 50 - assert layout.cells[2].geometry[0].height == 5 - assert layout.cells[2].geometry[0].x == 0 - assert layout.cells[2].geometry[0].y == 0 - - assert layout.cells[2].geometry[1].layer == 2 - assert layout.cells[2].geometry[1].datatype == 1 - assert layout.cells[2].geometry[1].string.string == 'TOP' - assert layout.cells[2].geometry[1].x == 0 - assert layout.cells[2].geometry[1].y == 0 diff --git a/fatamorgana/test/test_files_paths.py b/fatamorgana/test/test_files_paths.py deleted file mode 100644 index 1dc1def..0000000 --- a/fatamorgana/test/test_files_paths.py +++ /dev/null @@ -1,199 +0,0 @@ -# mypy: disable-error-code="union-attr" -from typing import IO -from io import BytesIO - -from numpy.testing import assert_equal - -from .utils import HEADER, FOOTER -from ..basic import write_uint, write_sint, write_bstring, write_byte, PathExtensionScheme -from ..main import OasisLayout - - -def base_tests(layout: OasisLayout) -> None: - assert layout.version.string == '1.0' - assert layout.unit == 1000 - assert layout.validation.checksum_type == 0 - - assert not layout.properties - assert not layout.propnames - assert not layout.xnames - assert not layout.textstrings - assert not layout.cellnames - assert not layout.layers - - assert len(layout.cells) == 1 - assert layout.cells[0].name.string == 'ABC' - assert not layout.cells[0].properties - - -def write_file_1(buf: IO[bytes]) -> IO[bytes]: - """ - """ - buf.write(HEADER) - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'ABC') # Cell name - - # PATH 0 - write_uint(buf, 22) # PATH record - write_byte(buf, 0b1111_1011) # EWPX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 10) # half-width - write_byte(buf, 0b0000_1111) # extension-scheme 0000_SSEE - write_sint(buf, 5) # (extension-scheme) start - write_sint(buf, -5) # (extension-scheme) end - write_uint(buf, 0) # pointlist: 1-delta, horiz-fisrt - write_uint(buf, 3) # (pointlist) dimension - write_sint(buf, 150) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, -50) # (pointlist) - write_sint(buf, 0) # geometry-x (absolute) - write_sint(buf, 100) # geometry-y (absolute) - - write_uint(buf, 16) # XYRELATIVE record - - # PATH 1 - write_uint(buf, 22) # PATH record - write_byte(buf, 0b1110_1011) # EWPX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 10) # half-width - write_byte(buf, 0b0000_0000) # extension-scheme 0000_SSEE - write_uint(buf, 0) # pointlist: 1-delta, horiz-fisrt - write_uint(buf, 3) # (pointlist) dimension - write_sint(buf, 150) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, -50) # (pointlist) - write_sint(buf, 200) # geometry-y (relative) - - # PATH 2 - write_uint(buf, 22) # PATH record - write_byte(buf, 0b1110_1001) # EWPX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 10) # half-width - write_byte(buf, 0b0000_0100) # extension-scheme 0000_SSEE - write_uint(buf, 0) # pointlist: 1-delta, horiz-fisrt - write_uint(buf, 3) # (pointlist) dimension - write_sint(buf, 150) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, -50) # (pointlist) - write_sint(buf, 200) # geometry-y (relative) - - # PATH 3 - write_uint(buf, 22) # PATH record - write_byte(buf, 0b1110_1010) # EWPX_YRDL - write_uint(buf, 2) # datatype - write_uint(buf, 12) # half-width - write_byte(buf, 0b0000_0101) # extension-scheme 0000_SSEE - write_uint(buf, 0) # pointlist: 1-delta, horiz-fisrt - write_uint(buf, 3) # (pointlist) dimension - write_sint(buf, 150) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, -50) # (pointlist) - write_sint(buf, 200) # geometry-y (relative) - - # PATH 4 - write_uint(buf, 22) # PATH record - write_byte(buf, 0b1010_1011) # EWPX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_byte(buf, 0b0000_1010) # extension-scheme 0000_SSEE - write_uint(buf, 0) # pointlist: 1-delta, horiz-fisrt - write_uint(buf, 3) # (pointlist) dimension - write_sint(buf, 150) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, -50) # (pointlist) - write_sint(buf, 200) # geometry-y (relative) - - # PATH 5 - write_uint(buf, 22) # PATH record - write_byte(buf, 0b0000_1011) # EWPX_YRDL - write_uint(buf, 2) # layer - write_uint(buf, 3) # datatype - write_sint(buf, 200) # geometry-y (relative) - - # PATH 6 - write_uint(buf, 22) # PATH record - write_byte(buf, 0b0000_1111) # EWPX_YRDL - write_uint(buf, 2) # layer - write_uint(buf, 3) # datatype - write_sint(buf, 200) # geometry-y (relative) - write_uint(buf, 1) # repetition (3x4 matrix) - write_uint(buf, 1) # (repetition) x-dimension - write_uint(buf, 2) # (repetition) y-dimension - write_uint(buf, 200) # (repetition) x-spacing - write_uint(buf, 300) # (repetition) y-spacing - - write_uint(buf, 16) # XYRELATIVE record - - # PATH 7 - write_uint(buf, 22) # PATH record - write_byte(buf, 0b0001_0101) # EWPX_YRDL - write_uint(buf, 1) # layer - write_sint(buf, 1000) # geometry-x (relative) - write_uint(buf, 0) # repetition (reuse) - - buf.write(FOOTER) - return buf - - -def test_file_1() -> None: - buf = write_file_1(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - - geometry = layout.cells[0].geometry - assert len(geometry) == 8 - - for ii, gg in enumerate(geometry): - msg = f'Failed on path {ii}' - if ii < 7: - assert gg.y == 100 + ii * 200, msg - assert gg.x == 0, msg - else: - assert gg.x == 1000, msg - assert gg.y == 1300, msg - - if ii < 5: - assert gg.layer == 1, msg - assert gg.datatype == 2, msg - elif ii < 7: - assert gg.layer == 2, msg - assert gg.datatype == 3, msg - else: - assert gg.layer == 1, msg - assert gg.datatype == 3, msg - - if ii < 6: - assert gg.repetition is None, msg - elif ii in (7, 8): - assert gg.repetition.a_count == 3, msg - assert gg.repetition.b_count == 4, msg - assert gg.repetition.a_vector == [200, 0], msg - assert gg.repetition.b_vector == [0, 300], msg - assert not gg.properties, msg - - if ii < 3: - assert gg.half_width == 10, msg - else: - assert gg.half_width == 12, msg - - assert len(gg.point_list) == 3, msg # type: ignore - assert_equal(gg.point_list, [[150, 0], [0, 50], [-50, 0]], err_msg=msg) - - if ii >= 4: - assert gg.extension_start == (PathExtensionScheme.HalfWidth, None) - assert gg.extension_end == (PathExtensionScheme.HalfWidth, None) - - assert geometry[0].extension_start == (PathExtensionScheme.Arbitrary, 5) - assert geometry[1].extension_start == (PathExtensionScheme.Arbitrary, 5) - assert geometry[2].extension_start == (PathExtensionScheme.Flush, None) - assert geometry[3].extension_start == (PathExtensionScheme.Flush, None) - assert geometry[0].extension_end == (PathExtensionScheme.Arbitrary, -5) - assert geometry[1].extension_end == (PathExtensionScheme.Arbitrary, -5) - assert geometry[2].extension_end == (PathExtensionScheme.Arbitrary, -5) - assert geometry[3].extension_end == (PathExtensionScheme.Flush, None) diff --git a/fatamorgana/test/test_files_placements.py b/fatamorgana/test/test_files_placements.py deleted file mode 100644 index ce4c8aa..0000000 --- a/fatamorgana/test/test_files_placements.py +++ /dev/null @@ -1,890 +0,0 @@ -# mypy: disable-error-code="union-attr" -from typing import IO, cast -from io import BytesIO - -from numpy.testing import assert_equal - -from .utils import HEADER, FOOTER -from ..basic import write_uint, write_sint, write_bstring, write_byte, write_float32, write_float64 -from ..records import Rectangle -from ..main import OasisLayout - - -def base_tests(layout: OasisLayout) -> None: - assert layout.version.string == '1.0' - assert layout.unit == 1000 - assert layout.validation.checksum_type == 0 - - assert not layout.properties - assert not layout.propnames - assert not layout.xnames - assert not layout.textstrings - assert not layout.layers - - -def write_rectangle(buf: IO[bytes], pos: tuple[int, int] = (300, -400)) -> None: - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0111_1011) # SWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 100) # width - write_uint(buf, 200) # height - write_sint(buf, pos[0]) # geometry-x (absolute) - write_sint(buf, pos[1]) # geometry-y (absolute) - - -def write_file_1(buf: IO[bytes]) -> IO[bytes]: - """ - """ - buf.write(HEADER) - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'A') # Cell name - - write_rectangle(buf) - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'TOP') # Cell name - - write_uint(buf, 16) # XYRELATIVE record - - # PLACEMENT 0 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b1011_0000) # CNXY_RAAF - write_bstring(buf, b'A') # cell reference - write_sint(buf, -300) # placement-x (relative) - write_sint(buf, 400) # placement-y (relative) - - # PLACEMENT 1 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0011_0000) # CNXY_RAAF - write_sint(buf, 0) # placement-x (relative) - write_sint(buf, 400) # placement-y (relative) - - # PLACEMENT 2 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0001_0000) # CNXY_RAAF - write_sint(buf, 400) # placement-y (relative) - - # PLACEMENT 3 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0010_0000) # CNXY_RAAF - write_sint(buf, 300) # placement-x (relative) - - write_uint(buf, 15) # XYABSOLUTE record - - # PLACEMENT 4 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0011_0001) # CNXY_RAAF - write_sint(buf, 700) # placement-x (absolute) - write_sint(buf, 400) # placement-y (absolute) - - write_uint(buf, 16) # XYRELATIVE record - - # PLACEMENT 5 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0001_0010) # CNXY_RAAF - write_sint(buf, 1000) # placement-y (relative) - - # PLACEMENT 6 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0001_0011) # CNXY_RAAF - write_sint(buf, 1000) # placement-y (relative) - - write_uint(buf, 15) # XYABSOLUTE record - - # PLACEMENT 7 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0011_1111) # CNXY_RAAF - write_sint(buf, 2000) # placement-x (absolute) - write_sint(buf, 0) # placement-y (absolute) - write_uint(buf, 1) # repetition (3x4 matrix) - write_uint(buf, 1) # (repetition) x-dimension - write_uint(buf, 2) # (repetition) y-dimension - write_uint(buf, 300) # (repetition) x-spacing - write_uint(buf, 300) # (repetition) y-spacing - - write_uint(buf, 16) # XYRELATIVE record - - # PLACEMENT 8 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0011_1111) # CNXY_RAAF - write_sint(buf, 2000) # placement-x (relative) - write_sint(buf, 0) # placement-y (relative) - write_uint(buf, 0) # repetition (reuse) - - # PLACEMENT 9 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0011_1111) # CNXY_RAAF - write_sint(buf, 2000) # placement-x (relative) - write_sint(buf, 0) # placement-y (relative) - write_uint(buf, 2) # repetition (3 cols.) - write_uint(buf, 1) # (repetition) count - write_uint(buf, 320) # (repetition) spacing - - # PLACEMENT 10 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0011_1111) # CNXY_RAAF - write_sint(buf, 2000) # placement-x (relative) - write_sint(buf, 0) # placement-y (relative) - write_uint(buf, 3) # repetition (4 rows) - write_uint(buf, 2) # (repetition) count - write_uint(buf, 310) # (repetition) spacing - - # PLACEMENT 11 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0011_1111) # CNXY_RAAF - write_sint(buf, 2000) # placement-x (relative) - write_sint(buf, 0) # placement-y (relative) - write_uint(buf, 4) # repetition (4 arbitrary cols.) - write_uint(buf, 2) # (repetition) dimension - write_uint(buf, 320) # (repetition) spacing - write_uint(buf, 330) # (repetition) spacing - write_uint(buf, 340) # (repetition) spacing - - # PLACEMENT 12 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0011_1111) # CNXY_RAAF - write_sint(buf, 2000) # placement-x (relative) - write_sint(buf, 0) # placement-y (relative) - write_uint(buf, 8) # repetition (3x4 matrix, arbitrary vectors) - write_uint(buf, 1) # (repetition) n-dimension - write_uint(buf, 2) # (repetition) m-dimension - write_uint(buf, 310 << 2 | 0b01) # (repetition) n-displacement g-delta: (310, 320) - write_sint(buf, 320) # (repetition g-delta) - write_uint(buf, 330 << 4 | 0b1010) # (repetition) m-displacement g-delta: 330-northwest (-330, 330) - - buf.write(FOOTER) - return buf - - -def test_file_1() -> None: - buf = write_file_1(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - - assert len(layout.cells) == 2 - assert layout.cells[0].name.string == 'A' - assert not layout.cells[0].properties - assert not layout.cells[0].placements - assert layout.cells[1].name.string == 'TOP' - assert not layout.cells[1].properties - assert not layout.cells[1].geometry - - geometry = cast(list[Rectangle], layout.cells[0].geometry) - assert len(geometry) == 1 - assert geometry[0].layer == 1 - assert geometry[0].datatype == 2 - assert geometry[0].width == 100 - assert geometry[0].height == 200 - assert geometry[0].x == 300 - assert geometry[0].y == -400 - - placements = layout.cells[1].placements - assert len(placements) == 13 - for ii, pp in enumerate(placements): - msg = f'Failed on placement {ii}' - - assert not pp.properties, msg - assert pp.name.string == 'A', msg - - if ii < 3: - assert pp.x == -300, msg - elif ii == 3: - assert pp.x == 0, msg - elif 4 <= ii < 7: - assert pp.x == 700, msg - else: - assert pp.x == 2000 * (ii - 6), msg - - if ii < 3: - assert pp.y == 400 * (ii + 1), msg - elif ii >= 7: - assert pp.y == 0, msg - - if ii < 4 or ii == 5: - assert not bool(pp.flip), msg - else: - assert bool(pp.flip), msg - - if ii < 5: - assert pp.angle == 0, msg - elif ii in (5, 6): - assert pp.angle == 90, msg - elif ii >= 7: - assert pp.angle == 270, msg - - if ii < 7: - assert pp.repetition is None, msg - elif ii in (7, 8): - assert pp.repetition.a_count == 3, msg - assert pp.repetition.b_count == 4, msg - assert pp.repetition.a_vector == [300, 0], msg - assert pp.repetition.b_vector == [0, 300], msg - - assert placements[3].y == 1200 - assert placements[4].y == 400 - assert placements[5].y == 1400 - assert placements[6].y == 2400 - - assert placements[9].repetition.a_count == 3 - assert placements[9].repetition.b_count is None - assert placements[9].repetition.a_vector == [320, 0] - assert placements[9].repetition.b_vector is None - - assert placements[10].repetition.a_count == 4 - assert placements[10].repetition.b_count is None - assert placements[10].repetition.a_vector == [0, 310] - assert placements[10].repetition.b_vector is None - - assert_equal(placements[11].repetition.x_displacements, [320, 330, 340]) - assert_equal(placements[11].repetition.y_displacements, [0, 0, 0]) - - assert placements[12].repetition.a_count == 3 - assert placements[12].repetition.b_count == 4 - assert placements[12].repetition.a_vector == [310, 320] - assert placements[12].repetition.b_vector == [-330, 330] - - -def write_file_common(buf: IO[bytes], variant: int) -> IO[bytes]: - """ - """ - assert variant in (2, 3, 5, 7), 'Error in test definition!' - - buf.write(HEADER) - - if variant in (3, 5): - write_uint(buf, 3) # CELLNAME record (implicit id 0) - write_bstring(buf, b'A') - - write_uint(buf, 3) # CELLNAME record (implicit id 1) - write_bstring(buf, b'TOP') - - if variant == 3: - write_uint(buf, 13) # CELL record (name ref.) - write_uint(buf, 0) # Cell name 0 (A) - - write_rectangle(buf) - - if variant == 2: - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'TOP') # Cell name - else: - write_uint(buf, 13) # CELL record (name ref.) - write_uint(buf, 1) # Cell name 1 (TOP) - - write_uint(buf, 16) # XYRELATIVE record - - # PLACEMENT 0 - write_uint(buf, 17) # PLACEMENT (simple) - if variant == 2: - write_byte(buf, 0b1011_0000) # CNXY_RAAF - write_bstring(buf, b'A') # cell reference - else: - write_byte(buf, 0b1111_0000) # CNXY_RAAF - write_uint(buf, 0) # cell reference - write_sint(buf, -300) # placement-x (relative) - write_sint(buf, 400) # placement-y (relative) - - # PLACEMENT 1 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0011_0000) # CNXY_RAAF - write_sint(buf, 0) # placement-x (relative) - write_sint(buf, 400) # placement-y (relative) - - # PLACEMENT 2 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0001_0000) # CNXY_RAAF - write_sint(buf, 400) # placement-y (relative) - - # PLACEMENT 3 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0010_0000) # CNXY_RAAF - write_sint(buf, 300) # placement-x (relative) - - write_uint(buf, 15) # XYABSOLUTE record - - # PLACEMENT 4 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0011_0001) # CNXY_RAAF - write_sint(buf, 700) # placement-x (absolute) - write_sint(buf, 400) # placement-y (absolute) - - write_uint(buf, 16) # XYRELATIVE record - - # PLACEMENT 5 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0001_0010) # CNXY_RAAF - write_sint(buf, 1000) # placement-y (relative) - - # PLACEMENT 6 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0001_0011) # CNXY_RAAF - write_sint(buf, 1000) # placement-y (relative) - - if variant == 2: - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'A') # Cell name - - write_rectangle(buf) - elif variant in (5, 7): - write_uint(buf, 13) # CELL record (name ref.) - write_uint(buf, 0) # Cell name 0 (A) - - write_rectangle(buf) - - if variant == 7: - write_uint(buf, 3) # CELLNAME record (implicit id 0) - write_bstring(buf, b'A') - - write_uint(buf, 3) # CELLNAME record (implicit id 1) - write_bstring(buf, b'TOP') - - buf.write(FOOTER) - return buf - - -def test_file_2() -> None: - buf = write_file_common(BytesIO(), 2) - - buf.seek(0) - layout = OasisLayout.read(buf) - - assert len(layout.cells) == 2 - assert layout.cells[0].name.string == 'TOP' - assert not layout.cells[0].properties - assert not layout.cells[0].geometry - assert layout.cells[1].name.string == 'A' - assert not layout.cells[1].properties - assert not layout.cells[1].placements - assert not layout.cellnames - - common_tests(layout, 2) - - -def test_file_3() -> None: - buf = write_file_common(BytesIO(), 3) - - buf.seek(0) - layout = OasisLayout.read(buf) - - assert len(layout.cells) == 2 - assert layout.cells[0].name == 0 - assert not layout.cells[1].properties - assert not layout.cells[1].geometry - assert layout.cells[1].name == 1 - assert not layout.cells[0].properties - assert not layout.cells[0].placements - - assert len(layout.cellnames) == 2 - assert layout.cellnames[0].nstring.string == 'A' - assert layout.cellnames[1].nstring.string == 'TOP' - - common_tests(layout, 3) - - -def test_file_4() -> None: - buf = write_file_4(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - assert len(layout.cells) == 2 - assert layout.cells[0].name == 1 - assert not layout.cells[0].properties - assert not layout.cells[0].geometry - assert layout.cells[1].name == 0 - assert not layout.cells[1].properties - assert not layout.cells[1].placements - - assert len(layout.cellnames) == 2 - assert layout.cellnames[0].nstring.string == 'A' - assert layout.cellnames[1].nstring.string == 'TOP' - - common_tests(layout, 4) - - for ii, pp in enumerate(layout.cells[0].placements): - msg = f'Fail on placement {ii}' - assert pp.repetition.a_count == 3, msg - assert pp.repetition.b_count == 4, msg - assert pp.repetition.a_vector == [20, 0], msg - assert pp.repetition.b_vector == [0, 30], msg - - -def test_file_5() -> None: - buf = write_file_common(BytesIO(), 5) - - buf.seek(0) - layout = OasisLayout.read(buf) - - assert len(layout.cells) == 2 - assert layout.cells[0].name == 1 - assert not layout.cells[0].properties - assert not layout.cells[0].geometry - assert layout.cells[1].name == 0 - assert not layout.cells[1].properties - assert not layout.cells[1].placements - - assert len(layout.cellnames) == 2 - assert layout.cellnames[0].nstring.string == 'A' - assert layout.cellnames[1].nstring.string == 'TOP' - - common_tests(layout, 5) - - -def test_file_7() -> None: - buf = write_file_common(BytesIO(), 7) - - buf.seek(0) - layout = OasisLayout.read(buf) - - assert len(layout.cells) == 2 - assert layout.cells[0].name == 1 - assert not layout.cells[0].properties - assert not layout.cells[0].geometry - assert layout.cells[1].name == 0 - assert not layout.cells[1].properties - assert not layout.cells[1].placements - - assert len(layout.cellnames) == 2 - assert layout.cellnames[0].nstring.string == 'A' - assert layout.cellnames[1].nstring.string == 'TOP' - - common_tests(layout, 7) - - -def common_tests(layout: OasisLayout, variant: int) -> None: - base_tests(layout) - - if variant == 3: - geom_cell = 0 - top_cell = 1 - else: - geom_cell = 1 - top_cell = 0 - - geometry = layout.cells[geom_cell].geometry - assert len(geometry) == 1 - assert geometry[0].layer == 1 - assert geometry[0].datatype == 2 - assert geometry[0].width == 100 - assert geometry[0].height == 200 - assert geometry[0].x == 300 - assert geometry[0].y == -400 - - placements = layout.cells[top_cell].placements - assert len(placements) == 7 - for ii, pp in enumerate(placements): - msg = f'Failed on placement {ii}' - - assert not pp.properties, msg - if variant == 2: - assert pp.name.string == 'A', msg - else: - assert pp.name == 0, msg - - if ii < 3: - assert pp.x == -300, msg - elif ii == 3: - assert pp.x == 0, msg - else: - assert pp.x == 700, msg - - if ii < 3: - assert pp.y == 400 * (ii + 1), msg - - if ii in (4, 6): - assert bool(pp.flip), msg - else: - assert not bool(pp.flip), msg - - if ii in (5, 6): - assert pp.angle == 90, msg - else: - assert pp.angle == 0, msg - - if variant != 4: - assert pp.repetition is None, msg - - assert placements[3].y == 1200 - assert placements[4].y == 400 - assert placements[5].y == 1400 - assert placements[6].y == 2400 - - -def write_file_4(buf: IO[bytes]) -> IO[bytes]: - """ - """ - buf.write(HEADER) - - write_uint(buf, 3) # CELLNAME record (implicit id 0) - write_bstring(buf, b'A') - - write_uint(buf, 3) # CELLNAME record (implicit id 1) - write_bstring(buf, b'TOP') - - write_uint(buf, 13) # CELL record (name ref.) - write_uint(buf, 1) # Cell name 1 (TOP) - - write_uint(buf, 16) # XYRELATIVE record - - # PLACEMENT 0 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b1111_1000) # CNXY_RAAF - write_uint(buf, 0) # cell reference - write_sint(buf, -300) # placement-x (relative) - write_sint(buf, 400) # placement-y (relative) - write_uint(buf, 1) # repetition (3x4 matrix) - write_uint(buf, 1) # (repetition) x-dimension - write_uint(buf, 2) # (repetition) y-dimension - write_uint(buf, 20) # (repetition) x-spacing - write_uint(buf, 30) # (repetition) y-spacing - - # PLACEMENT 1 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0011_1000) # CNXY_RAAF - write_sint(buf, 0) # placement-x (relative) - write_sint(buf, 400) # placement-y (relative) - write_uint(buf, 0) # repetition (reuse) - - # PLACEMENT 2 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0001_1000) # CNXY_RAAF - write_sint(buf, 400) # placement-y (relative) - write_uint(buf, 0) # repetition (reuse) - - # PLACEMENT 3 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0010_1000) # CNXY_RAAF - write_sint(buf, 300) # placement-x (relative) - write_uint(buf, 0) # repetition (reuse) - - write_uint(buf, 15) # XYABSOLUTE record - - # PLACEMENT 4 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0011_1001) # CNXY_RAAF - write_sint(buf, 700) # placement-x (absolute) - write_sint(buf, 400) # placement-y (absolute) - write_uint(buf, 0) # repetition (reuse) - - write_uint(buf, 16) # XYRELATIVE record - - # PLACEMENT 5 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0001_1010) # CNXY_RAAF - write_sint(buf, 1000) # placement-y (relative) - write_uint(buf, 0) # repetition (reuse) - - # PLACEMENT 6 - write_uint(buf, 17) # PLACEMENT (simple) - write_byte(buf, 0b0001_1011) # CNXY_RAAF - write_sint(buf, 1000) # placement-y (relative) - write_uint(buf, 0) # repetition (reuse) - - write_uint(buf, 13) # CELL record (name ref.) - write_uint(buf, 0) # Cell name 0 (A) - - write_rectangle(buf) - - buf.write(FOOTER) - return buf - - -def write_file_6(buf: IO[bytes]) -> IO[bytes]: - """ - """ - buf.write(HEADER) - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'TOPTOP') # Cell name - - write_uint(buf, 16) # XYRELATIVE record - - write_uint(buf, 18) # PLACEMENT (mag 0.5, manhattan) - write_byte(buf, 0b1011_0110) # CNXY_RMAF - write_bstring(buf, b'TOP') # Cell reference - write_uint(buf, 6) # magnitude, float32 - write_float32(buf, 0.5) # (magnitude) - write_uint(buf, 7) # angle, float64 - write_float64(buf, 90.0) # (angle) - write_sint(buf, 100) # placement-x (relative) - write_sint(buf, 0) # placement-y (relative) - - write_uint(buf, 18) # PLACEMENT (no mag, manhattan) - write_byte(buf, 0b0011_0000) # CNXY_RMAF - write_sint(buf, 100) # placement-x (relative) - write_sint(buf, 1000) # placement-y (relative) - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'TOP') # Cell name - - write_uint(buf, 16) # XYRELATIVE record - - # PLACEMENT 0 - write_uint(buf, 18) # PLACEMENT (mag 0.5, manhattan) - write_byte(buf, 0b1011_0110) # CNXY_RMAF - write_bstring(buf, b'A') # Cell reference - write_uint(buf, 6) # magnitude, float32 - write_float32(buf, 0.5) # (magnitude) - write_uint(buf, 7) # angle, float64 - write_float64(buf, 0.0) # (angle) - write_sint(buf, -150) # placement-x (relative) - write_sint(buf, 200) # placement-y (relative) - - # PLACEMENT 1 - write_uint(buf, 18) # PLACEMENT (no mag, manhattan) - write_byte(buf, 0b0011_0000) # CNXY_RMAF - write_sint(buf, -150) # placement-x (relative) - write_sint(buf, 600) # placement-y (relative) - - # PLACEMENT 2 - write_uint(buf, 18) # PLACEMENT (no mag, manhattan) - write_byte(buf, 0b0001_0000) # CNXY_RMAF - write_sint(buf, 400) # placement-y (relative) - - # PLACEMENT 3 - write_uint(buf, 18) # PLACEMENT (no mag, manhattan) - write_byte(buf, 0b0010_0000) # CNXY_RMAF - write_sint(buf, 300) # placement-x (relative) - - write_uint(buf, 15) # XYABSOLUTE record - - # PLACEMENT 4 - write_uint(buf, 18) # PLACEMENT (no mag, manhattan) - write_byte(buf, 0b0011_0001) # CNXY_RMAF - write_sint(buf, 700) # placement-x (absolute) - write_sint(buf, 400) # placement-y (absolute) - - write_uint(buf, 16) # XYRELATIVE record - - # PLACEMENT 5 - write_uint(buf, 18) # PLACEMENT (no mag, manhattan) - write_byte(buf, 0b0001_0010) # CNXY_RMAF - write_uint(buf, 0) # angle (uint, positive) - write_uint(buf, 90) # (angle) - write_sint(buf, 1000) # placement-y (relative) - - # PLACEMENT 6 - write_uint(buf, 18) # PLACEMENT (no mag, manhattan) - write_byte(buf, 0b0001_0011) # CNXY_RMAF - write_uint(buf, 1) # angle (uint, negative) - write_uint(buf, 90) # (angle) - write_sint(buf, 1000) # placement-y (relative) - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'A') # Cell name - - write_rectangle(buf) - - buf.write(FOOTER) - return buf - - -def test_file_6() -> None: - buf = write_file_6(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - - assert len(layout.cells) == 3 - assert layout.cells[0].name.string == 'TOPTOP' - assert not layout.cells[0].properties - assert not layout.cells[0].geometry - assert layout.cells[1].name.string == 'TOP' - assert not layout.cells[1].properties - assert not layout.cells[1].geometry - assert layout.cells[2].name.string == 'A' - assert not layout.cells[2].properties - assert not layout.cells[2].placements - - geometry = layout.cells[2].geometry - assert len(geometry) == 1 - assert geometry[0].layer == 1 - assert geometry[0].datatype == 2 - assert geometry[0].width == 100 - assert geometry[0].height == 200 - assert geometry[0].x == 300 - assert geometry[0].y == -400 - - placements = layout.cells[1].placements - assert len(placements) == 7 - for ii, pp in enumerate(placements): - msg = f'Failed on placement {ii} in cell TOP' - - assert not pp.properties, msg - assert pp.name.string == 'A', msg - assert pp.flip == (ii in (4, 6)), msg - assert pp.repetition is None, msg - - if ii == 0: - assert pp.magnification == 0.5, msg - else: - assert pp.magnification is None, msg - - assert pp.x == [-150, -300, -300, 0, 700, 700, 700][ii], msg - assert pp.y == [200, 800, 1200, 1200, 400, 1400, 2400][ii], msg - assert pp.angle == [0, None, None, None, None, 90, -90][ii], msg - - placements2 = layout.cells[0].placements - assert len(placements2) == 2 - for ii, pp in enumerate(placements2): - msg = f'Failed on placement {ii} in cell TOPTOP' - - assert not pp.properties, msg - assert pp.name.string == 'TOP', msg - assert not pp.flip, msg - assert pp.repetition is None, msg - - assert pp.angle == [90, None][ii], msg - assert pp.magnification == [0.5, None][ii], msg - assert pp.x == [100, 200][ii], msg - assert pp.y == [0, 1000][ii], msg - - -def write_file_8(buf: IO[bytes]) -> IO[bytes]: - """ - """ - buf.write(HEADER) - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'TOPTOP') # Cell name - - write_uint(buf, 15) # XYABSOLUTE record - - write_uint(buf, 18) # PLACEMENT (mag 0.5, arbitrary angle) - write_byte(buf, 0b1011_0110) # CNXY_RMAF - write_bstring(buf, b'TOP') # Cell reference - write_uint(buf, 6) # magnitude, float32 - write_float32(buf, 0.5) # (magnitude) - write_uint(buf, 7) # angle, float64 - write_float64(buf, 22.5) # (angle) - write_sint(buf, 100) # placement-x (absolute) - write_sint(buf, 0) # placement-y (absolute) - - write_uint(buf, 18) # PLACEMENT (mag 1.0, manhattan) - write_byte(buf, 0b1011_0110) # CNXY_RMAF - write_bstring(buf, b'TOP') # Cell reference - write_uint(buf, 6) # magnitude, float32 - write_float32(buf, 1.0) # (magnitude) - write_uint(buf, 7) # angle, float64 - write_float64(buf, 0.0) # (angle) - write_sint(buf, 1100) # placement-x (absolute) - write_sint(buf, 0) # placement-y (absolute) - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'TOP') # Cell name - - write_uint(buf, 18) # PLACEMENT (mag 2.0, manhattan) - write_byte(buf, 0b1011_0110) # CNXY_RMAF - write_bstring(buf, b'A') # Cell reference - write_uint(buf, 6) # magnitude, float32 - write_float32(buf, 2.0) # (magnitude) - write_uint(buf, 7) # angle, float64 - write_float64(buf, 0.0) # (angle) - write_sint(buf, -100) # placement-x (absolute) - write_sint(buf, 100) # placement-y (absolute) - - write_uint(buf, 18) # PLACEMENT (mag 1.0, arbitrary angle) - write_byte(buf, 0b1011_0110) # CNXY_RMAF - write_bstring(buf, b'A') # Cell reference - write_uint(buf, 6) # magnitude, float32 - write_float32(buf, 1.0) # (magnitude) - write_uint(buf, 7) # angle, float64 - write_float64(buf, 45.0) # (angle) - write_sint(buf, -150) # placement-x (absolute) - write_sint(buf, 1100) # placement-y (absolute) - - write_uint(buf, 18) # PLACEMENT (mag 0.5, arbitrary angle) - write_byte(buf, 0b1011_1111) # CNXY_RMAF - write_bstring(buf, b'A') # Cell reference - write_uint(buf, 6) # magnitude, float32 - write_float32(buf, 0.5) # (magnitude) - write_uint(buf, 7) # angle, float64 - write_float64(buf, 135.0) # (angle) - write_sint(buf, -200) # placement-x (absolute) - write_sint(buf, 2100) # placement-y (absolute) - write_uint(buf, 1) # repetition (3x4 matrix) - write_uint(buf, 1) # (repetition) x-dimension - write_uint(buf, 2) # (repetition) y-dimension - write_uint(buf, 200) # (repetition) x-spacing - write_uint(buf, 300) # (repetition) y-spacing - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'A') # Cell name - - write_rectangle(buf, pos=(30, -40)) - - buf.write(FOOTER) - return buf - - -def test_file_8() -> None: - buf = write_file_8(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - - assert len(layout.cells) == 3 - assert layout.cells[0].name.string == 'TOPTOP' - assert not layout.cells[0].properties - assert not layout.cells[0].geometry - assert layout.cells[1].name.string == 'TOP' - assert not layout.cells[1].properties - assert not layout.cells[1].geometry - assert layout.cells[2].name.string == 'A' - assert not layout.cells[2].properties - assert not layout.cells[2].placements - - geometry = cast(list[Rectangle], layout.cells[2].geometry) - assert len(geometry) == 1 - assert geometry[0].layer == 1 - assert geometry[0].datatype == 2 - assert geometry[0].width == 100 - assert geometry[0].height == 200 - assert geometry[0].x == 30 - assert geometry[0].y == -40 - - placements = layout.cells[1].placements - assert len(placements) == 3 - for ii, pp in enumerate(placements): - msg = f'Failed on placement {ii} in cell TOP' - - assert not pp.properties, msg - assert pp.name.string == 'A', msg - assert pp.flip == (ii == 2), msg - - assert pp.magnification == [2, 1, 0.5][ii], msg - assert pp.angle == [0, 45, 135][ii], msg - - assert pp.x == [-100, -150, -200][ii], msg - assert pp.y == [100, 1100, 2100][ii], msg - - if ii < 2: - assert pp.repetition is None, msg - assert placements[2].repetition.a_count == 3 - assert placements[2].repetition.b_count == 4 - assert placements[2].repetition.a_vector == [200, 0] - assert placements[2].repetition.b_vector == [0, 300] - - placements2 = layout.cells[0].placements - assert len(placements2) == 2 - for ii, pp in enumerate(placements2): - msg = f'Failed on placement {ii} in cell TOPTOP' - - assert not pp.properties, msg - assert pp.name.string == 'TOP', msg - assert not pp.flip, msg - assert pp.repetition is None, msg - - assert pp.angle == [22.5, 0][ii], msg - assert pp.magnification == [0.5, 1.0][ii], msg - assert pp.x == [100, 1100][ii], msg - assert pp.y == [0, 0][ii], msg diff --git a/fatamorgana/test/test_files_polygons.py b/fatamorgana/test/test_files_polygons.py deleted file mode 100644 index 7ac0102..0000000 --- a/fatamorgana/test/test_files_polygons.py +++ /dev/null @@ -1,451 +0,0 @@ -# mypy: disable-error-code="union-attr, arg-type" -from typing import IO -from io import BytesIO - -import numpy -from numpy.testing import assert_equal - -from .utils import HEADER, FOOTER -from ..basic import write_uint, write_sint, write_bstring, write_byte -from ..main import OasisLayout - - -def base_tests(layout: OasisLayout) -> None: - assert layout.version.string == '1.0' - assert layout.unit == 1000 - assert layout.validation.checksum_type == 0 - - assert not layout.properties - assert not layout.xnames - assert not layout.textstrings - assert not layout.cellnames - assert not layout.layers - - assert len(layout.cells) == 1 - assert layout.cells[0].name.string == 'ABC' - assert not layout.cells[0].properties - - -def common_tests(layout: OasisLayout) -> None: - geometry = layout.cells[0].geometry - assert len(geometry) == 12 - - assert geometry[0].x == 0 - assert geometry[0].y == 100 - assert geometry[1].x == -200 - assert geometry[1].y == 400 - assert geometry[2].x == 0 - assert geometry[2].y == 400 - assert geometry[3].x == 0 - assert geometry[3].y == 1000 - assert geometry[4].x == 200 - assert geometry[4].y == 1000 - assert geometry[5].x == 400 - assert geometry[5].y == 1000 - assert geometry[6].x == 700 - assert geometry[6].y == 1000 - assert geometry[7].x == 900 - assert geometry[7].y == 1000 - assert geometry[8].x == 1100 - assert geometry[8].y == 1000 - assert geometry[9].x == 0 - assert geometry[9].y == 2000 - assert geometry[10].x == 1000 - assert geometry[10].y == 2000 - assert geometry[11].x == 2000 - assert geometry[11].y == 2000 - - for ii, gg in enumerate(geometry): - msg = f'Failed on polygon {ii}' - if ii < 2: - assert gg.layer == 1, msg - assert gg.datatype == 2, msg - elif ii < 10: - assert gg.layer == 2, msg - assert gg.datatype == 3, msg - else: - assert gg.layer == 2, msg - assert gg.datatype == 1, msg - - if ii < 9: - assert gg.repetition is None, msg - elif ii in (9, 10): - assert gg.repetition.a_count == 3, msg - assert gg.repetition.b_count == 4, msg - assert gg.repetition.a_vector == [200, 0], msg - assert gg.repetition.b_vector == [0, 300], msg - - assert geometry[11].repetition.y_displacements == [200, 300] - - for ii in range(4): - msg = f'Fail on poly {ii}' - assert len(geometry[0].point_list) == 6, msg - assert_equal( - geometry[0].point_list, - [[150, 0], [0, 50], [-50, 0], [0, 50], [-100, 0], [0, -100]], - err_msg=msg, - ) - assert len(geometry[4].point_list) == 6 - assert_equal(geometry[4].point_list, [[0, 150], [50, 0], [0, -50], [50, 0], [0, -100], [-100, 0]]) - - assert len(geometry[5].point_list) == 8 - assert_equal(geometry[5].point_list, [[150, 0], [0, 50], [-50, 0], [0, 50], [-50, 0], [0, -50], [-50, 0], [0, -50]]) - assert len(geometry[6].point_list) == 9 - assert_equal(geometry[6].point_list, [[25, 0], [50, 50], [0, 50], [-50, 50], [-50, 0], [-50, -50], [0, -50], [50, -50], [25, 0]]) - assert len(geometry[7].point_list) == 9 - assert_equal(geometry[7].point_list, [[25, 0], [50, 50], [0, 50], [-50, 50], [-50, 0], [-50, -50], [10, -75], [25, -25], [40, 0]]) - assert len(geometry[8].point_list) == 9 - assert_equal( - geometry[8].point_list, - numpy.cumsum([[25, 0], [50, 50], [0, 50], [-50, 50], [-50, 0], [-50, -50], [10, -75], [25, -25], [45, -575]], axis=0), - ) - - for ii in range(9, 12): - msg = f'Fail on poly {ii}' - assert len(geometry[ii].point_list) == 6, msg - assert_equal(geometry[ii].point_list, [[0, 150], [50, 0], [0, -50], [50, 0], [0, -100], [-100, 0]], err_msg=msg) - - -def write_file_common(buf: IO[bytes], variant: int) -> IO[bytes]: - """ - """ - assert variant in (1, 3), 'Error in test!!' - - buf.write(HEADER) - - if variant == 3: - write_uint(buf, 7) # PROPNAME record (implict id 0) - write_bstring(buf, b'PROP0') # property name - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'ABC') # Cell name - - # POLYGON 0 - write_uint(buf, 21) # POLYGON record - write_byte(buf, 0b0011_1011) # 00PX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 0) # pointlist: 1-delta, horiz-fisrt - write_uint(buf, 4) # (pointlist) dimension - write_sint(buf, 150) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, -50) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, 0) # geometry-x (absolute) - write_sint(buf, 100) # geometry-y (absolute) - - if variant == 3: - # PROPERTY 0 - write_uint(buf, 28) # PROPERTY record (explicit) - write_byte(buf, 0b0001_0110) # UUUU_VCNS - write_uint(buf, 0) # propname id - write_uint(buf, 2) # property value (real: positive reciprocal) - write_uint(buf, 5) # (real) 1/5 - - write_uint(buf, 16) # XYRELATIVE record - - # Polygon 1 - write_uint(buf, 21) # POLYGON record - write_byte(buf, 0b0011_1011) # 00PX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 0) # pointlist: 1-delta, horiz-fisrt - write_uint(buf, 4) # (pointlist) dimension - write_sint(buf, 150) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, -50) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, -200) # geometry-x (relative) - write_sint(buf, 300) # geometry-y (relative) - - if variant == 3: - # PROPERTY 1 - write_uint(buf, 29) # PROPERTY record (repeat) - - write_uint(buf, 15) # XYABSOLUTE record - - # Polygon 2 - write_uint(buf, 21) # POLYGON record - write_byte(buf, 0b0011_0011) # 00PX_YRDL - write_uint(buf, 2) # layer - write_uint(buf, 3) # datatype - write_uint(buf, 0) # pointlist: 1-delta, horiz-fisrt - write_uint(buf, 4) # (pointlist) dimension - write_sint(buf, 150) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, -50) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, 0) # geometry-x (absolute) - - if variant == 3: - # PROPERTY 2 - write_uint(buf, 29) # PROPERTY record (repeat) - - # Polygon 3 - write_uint(buf, 21) # POLYGON record - write_byte(buf, 0b0000_1000) # 00PX_YRDL - write_sint(buf, 1000) # geometry-y (absolute) - - if variant == 3: - # PROPERTY 3 - write_uint(buf, 29) # PROPERTY record (repeat) - - # Polygon 4 - write_uint(buf, 21) # POLYGON record - write_byte(buf, 0b0011_0011) # 00PX_YRDL - write_uint(buf, 2) # layer - write_uint(buf, 3) # datatype - write_uint(buf, 1) # pointlist: 1-delta, vert-fisrt - write_uint(buf, 4) # (pointlist) dimension - write_sint(buf, 150) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, -50) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, 200) # geometry-x (absolute) - - if variant == 3: - # PROPERTY 4 - write_uint(buf, 29) # PROPERTY record (repeat) - - # Polygon 5 - write_uint(buf, 21) # POLYGON record - write_byte(buf, 0b0011_0011) # 00PX_YRDL - write_uint(buf, 2) # layer - write_uint(buf, 3) # datatype - write_uint(buf, 2) # pointlist: 2-delta - write_uint(buf, 7) # (pointlist) dimension - write_uint(buf, 150 << 2 | 0b00) # (pointlist) - write_uint(buf, 50 << 2 | 0b01) # (pointlist) - write_uint(buf, 50 << 2 | 0b10) # (pointlist) - write_uint(buf, 50 << 2 | 0b01) # (pointlist) - write_uint(buf, 50 << 2 | 0b10) # (pointlist) - write_uint(buf, 50 << 2 | 0b11) # (pointlist) - write_uint(buf, 50 << 2 | 0b10) # (pointlist) - write_sint(buf, 400) # geometry-x (absolute) - - if variant == 3: - # PROPERTY 5 - write_uint(buf, 29) # PROPERTY record (repeat) - - # Polygon 6 - write_uint(buf, 21) # POLYGON record - write_byte(buf, 0b0011_0011) # 00PX_YRDL - write_uint(buf, 2) # layer - write_uint(buf, 3) # datatype - write_uint(buf, 3) # pointlist: 3-delta - write_uint(buf, 8) # (pointlist) dimension - write_uint(buf, 25 << 3 | 0b000) # (pointlist) - write_uint(buf, 50 << 3 | 0b100) # (pointlist) - write_uint(buf, 50 << 3 | 0b001) # (pointlist) - write_uint(buf, 50 << 3 | 0b101) # (pointlist) - write_uint(buf, 50 << 3 | 0b010) # (pointlist) - write_uint(buf, 50 << 3 | 0b110) # (pointlist) - write_uint(buf, 50 << 3 | 0b011) # (pointlist) - write_uint(buf, 50 << 3 | 0b111) # (pointlist) - write_sint(buf, 700) # geometry-x (absolute) - - if variant == 3: - # PROPERTY 6 - write_uint(buf, 29) # PROPERTY record (repeat) - - # Polygon 7 - write_uint(buf, 21) # POLYGON record - write_byte(buf, 0b0011_0011) # 00PX_YRDL - write_uint(buf, 2) # layer - write_uint(buf, 3) # datatype - write_uint(buf, 4) # pointlist: g-delta - write_uint(buf, 8) # (pointlist) dimension - write_uint(buf, 25 << 4 | 0b0000) # (pointlist) - write_uint(buf, 50 << 4 | 0b1000) # (pointlist) - write_uint(buf, 50 << 4 | 0b0010) # (pointlist) - write_uint(buf, 50 << 2 | 0b11) # (pointlist) - write_sint(buf, 50) - write_uint(buf, 50 << 4 | 0b0100) # (pointlist) - write_uint(buf, 50 << 4 | 0b1100) # (pointlist) - write_uint(buf, 10 << 2 | 0b01) # (pointlist) - write_sint(buf, -75) - write_uint(buf, 25 << 4 | 0b1110) # (pointlist) - write_sint(buf, 900) # geometry-x (absolute) - - if variant == 3: - # PROPERTY 7 - write_uint(buf, 29) # PROPERTY record (repeat) - - # Polygon 8 - write_uint(buf, 21) # POLYGON record - write_byte(buf, 0b0011_0011) # 00PX_YRDL - write_uint(buf, 2) # layer - write_uint(buf, 3) # datatype - write_uint(buf, 5) # pointlist: double g-delta - write_uint(buf, 8) # (pointlist) dimension - write_uint(buf, 25 << 4 | 0b0000) # (pointlist) - write_uint(buf, 50 << 4 | 0b1000) # (pointlist) - write_uint(buf, 50 << 4 | 0b0010) # (pointlist) - write_uint(buf, 50 << 2 | 0b11) # (pointlist) - write_sint(buf, 50) - write_uint(buf, 50 << 4 | 0b0100) # (pointlist) - write_uint(buf, 50 << 4 | 0b1100) # (pointlist) - write_uint(buf, 10 << 2 | 0b01) # (pointlist) - write_sint(buf, -75) - write_uint(buf, 25 << 4 | 0b1110) # (pointlist) - write_sint(buf, 1100) # geometry-x (absolute) - - if variant == 3: - # PROPERTY 8 - write_uint(buf, 29) # PROPERTY record (repeat) - - # Polygon 9 - write_uint(buf, 21) # POLYGON record - write_byte(buf, 0b0011_1111) # 00PX_YRDL - write_uint(buf, 2) # layer - write_uint(buf, 3) # datatype - write_uint(buf, 1) # pointlist: 1-delta (vert. first) - write_uint(buf, 4) # (pointlist) dimension - write_sint(buf, 150) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, -50) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, 0) # geometry-x (absolute) - write_sint(buf, 2000) # geometry-y (absolute) - write_uint(buf, 1) # repetition (3x4 matrix) - write_uint(buf, 1) # (repetition) x-dimension - write_uint(buf, 2) # (repetition) y-dimension - write_uint(buf, 200) # (repetition) x-spacing - write_uint(buf, 300) # (repetition) y-spacing - - if variant == 3: - # PROPERTY 9 - write_uint(buf, 29) # PROPERTY record (repeat) - - write_uint(buf, 16) # XYRELATIVE record - - # Polygon 10 - write_uint(buf, 21) # POLYGON record - write_byte(buf, 0b0011_0110) # 00PX_YRDL - write_uint(buf, 1) # datatype - write_uint(buf, 1) # pointlist: 1-delta (vert. first) - write_uint(buf, 4) # (pointlist) dimension - write_sint(buf, 150) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, -50) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, 1000) # geometry-x (relative) - write_uint(buf, 0) # repetition (reuse) - - if variant == 3: - # PROPERTY 10 - write_uint(buf, 29) # PROPERTY record (repeat) - - # Polygon 11 - write_uint(buf, 21) # POLYGON record - write_byte(buf, 0b0011_0110) # 00PX_YRDL - write_uint(buf, 1) # datatype - write_uint(buf, 1) # pointlist: 1-delta (vert. first) - write_uint(buf, 4) # (pointlist) dimension - write_sint(buf, 150) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, -50) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, 1000) # geometry-x (relative) - write_uint(buf, 6) # repetition (3 rows) - write_uint(buf, 1) # (repetition) dimension - write_uint(buf, 200) # (repetition) y-delta - write_uint(buf, 300) # (repetition) y-delta - - if variant == 3: - # PROPERTY 11 - write_uint(buf, 29) # PROPERTY record (repeat) - - buf.write(FOOTER) - return buf - - -def test_file_1() -> None: - buf = write_file_common(BytesIO(), 1) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - common_tests(layout) - assert not layout.propnames - - geometry = layout.cells[0].geometry - for ii, gg in enumerate(geometry): - assert not gg.properties, f'Fail on polygon {ii}' - - -def write_file_2(buf: IO[bytes]) -> IO[bytes]: - """ - """ - buf.write(HEADER) - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'ABC') # Cell name - - write_uint(buf, 15) # XYRELATIVE record - - # POLYGON 0 - write_uint(buf, 21) # POLYGON record - write_byte(buf, 0b0011_0011) # 00PX_YRDL - write_uint(buf, 2) # layer - write_uint(buf, 3) # datatype - write_uint(buf, 4) # pointlist: g-delta - write_uint(buf, 8002) # (pointlist) dimension - write_uint(buf, 1000 << 2 | 0b11) # (pointlist) - write_sint(buf, 0) # (pointlist) - for _ in range(4000): - write_uint(buf, 10 << 2 | 0b01) # (pointlist) - write_sint(buf, 20) # (pointlist) - write_uint(buf, 10 << 2 | 0b11) # (pointlist) - write_sint(buf, 20) # (pointlist) - write_uint(buf, 1000 << 2 | 0b01) # (pointlist) - write_sint(buf, 0) # (pointlist) - write_sint(buf, 0) # geometry-x (absolute) - - buf.write(FOOTER) - return buf - - -def test_file_2() -> None: - buf = write_file_2(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - assert len(layout.cells[0].geometry) == 1 - - poly = layout.cells[0].geometry[0] - assert poly.layer == 2 - assert poly.datatype == 3 - assert poly.x == 0 - assert poly.y == 0 - assert len(poly.point_list) == 8002 + 1 - assert_equal(poly.point_list, - ([[-1000, 0]] - + [[(-1) ** nn * 10, 20] for nn in range(8000)] - + [[1000, 0], [0, -20 * 8000]]), - ) - - -def test_file_3() -> None: - buf = write_file_common(BytesIO(), 3) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - common_tests(layout) - - assert len(layout.propnames) == 1 - assert layout.propnames[0].string == 'PROP0' - - geometry = layout.cells[0].geometry - for ii, gg in enumerate(geometry): - msg = f'Fail on polygon {ii}' - assert len(gg.properties) == 1, msg - assert gg.properties[0].name == 0, msg # type: ignore - assert len(gg.properties[0].values) == 1, msg - assert gg.properties[0].values[0] * 5 == 1, msg # type: ignore - diff --git a/fatamorgana/test/test_files_properties.py b/fatamorgana/test/test_files_properties.py deleted file mode 100644 index 98fdede..0000000 --- a/fatamorgana/test/test_files_properties.py +++ /dev/null @@ -1,1078 +0,0 @@ -# mypy: disable-error-code="union-attr, index, arg-type" -from typing import IO -from io import BytesIO - -import pytest -from numpy.testing import assert_equal - -from .utils import HEADER, FOOTER -from ..basic import write_uint, write_sint, write_bstring, write_byte -from ..basic import InvalidDataError -from ..main import OasisLayout - - -def base_tests(layout: OasisLayout) -> None: - assert layout.version.string == '1.0' - assert layout.unit == 1000 - assert layout.validation.checksum_type == 0 - - assert not layout.properties - assert not layout.xnames - assert not layout.textstrings - assert not layout.cellnames - assert not layout.layers - - -def write_file_common(buf: IO[bytes], variant: int) -> IO[bytes]: - """ - """ - include_repetitions = variant in (2, 5) - - def var_byte(buf: IO[bytes], byte: int) -> None: - if include_repetitions: - byte |= 0b0100 - write_byte(buf, byte) - - buf.write(HEADER) - - if variant in (1, 2): - write_uint(buf, 10) # PROPSTRING (explicit id) - write_bstring(buf, b'PropStringId12') - write_uint(buf, 12) # id - - write_uint(buf, 10) # PROPSTRING record (explicit id) - write_bstring(buf, b'Property string value for ID 13') - write_uint(buf, 13) # id - - write_uint(buf, 7) # PROPNAME record (implicit id 0) - write_bstring(buf, b'PROP0') - - write_uint(buf, 7) # PROPNAME record (implicit id 1) - write_bstring(buf, b'PROP1') - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'A') # Cell name - - write_uint(buf, 16) # XYRELATIVE record - - # RECTANGLE 0 - write_uint(buf, 20) # RECTANGLE record - var_byte(buf, 0b0110_0011) # SWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 100) # width - write_uint(buf, 200) # height - if include_repetitions: - write_uint(buf, 1) # repetition (3x2 matrix) - write_uint(buf, 1) # (repetition) x-dimension - write_uint(buf, 0) # (repetition) y-dimension - write_uint(buf, 300) # (repetition) x-spacing - write_uint(buf, 320) # (repetition) y-spacing - - write_uint(buf, 28) # PROPERTY record - write_byte(buf, 0b0000_0100) # UUUU_VCNS - write_bstring(buf, b'PROPX') - - # RECTANGLE 1 - write_uint(buf, 20) # RECTANGLE record - var_byte(buf, 0b0111_1011) # SWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 100) # width - write_uint(buf, 200) # height - write_sint(buf, 0) # geometry-x (relative) - write_sint(buf, 1000) # geometry-y (relative) - if include_repetitions: - write_uint(buf, 0) # repetition (reuse) - - write_uint(buf, 28) # PROPERTY record - write_byte(buf, 0b0001_0110) # UUUU_VCNS - write_uint(buf, 0) # propname id - write_uint(buf, 1) # property value 0 (real type 1, negative int) - write_uint(buf, 5) # (real 1) - - # RECTANGLE 2 - write_uint(buf, 20) # RECTANGLE record - var_byte(buf, 0b0111_1011) # SWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 100) # width - write_uint(buf, 200) # height - write_sint(buf, 0) # geometry-x (relative) - write_sint(buf, 1000) # geometry-y (relative) - if include_repetitions: - write_uint(buf, 0) # repetition (reuse) - - write_uint(buf, 28) # PROPERTY record - write_byte(buf, 0b0100_0110) # UUUU_VCNS - write_uint(buf, 0) # propname id - write_uint(buf, 8) # prop value 0 (unsigned int) - write_uint(buf, 25) # (prop value) - write_uint(buf, 9) # prop value 1 (signed int) - write_sint(buf, -124) # (prop value) - write_uint(buf, 10) # prop value 2 (a-string) - write_bstring(buf, b'PROP_VALUE2') - write_uint(buf, 13) # prop value 3 (propstring ref.) - write_uint(buf, 12) - - # RECTANGLE 3 - write_uint(buf, 20) # RECTANGLE record - var_byte(buf, 0b0111_1011) # SWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 100) # width - write_uint(buf, 200) # height - write_sint(buf, 0) # geometry-x (relative) - write_sint(buf, 1000) # geometry-y (relative) - if include_repetitions: - write_uint(buf, 0) # repetition (reuse) - - write_uint(buf, 28) # PROPERTY record - write_byte(buf, 0b1111_0000) # UUUU_VCNS - write_uint(buf, 3) # number of values - write_uint(buf, 0) # prop value 0 (unsigned int) - write_uint(buf, 25) # (prop value) - write_uint(buf, 9) # prop value 1 (signed int) - write_sint(buf, -124) # (prop value) - write_uint(buf, 14) # prop value 2 (propstring ref.) - write_uint(buf, 13) - - # RECTANGLE 4 - write_uint(buf, 20) # RECTANGLE record - var_byte(buf, 0b0111_1011) # SWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 100) # width - write_uint(buf, 200) # height - write_sint(buf, 0) # geometry-x (relative) - write_sint(buf, 1000) # geometry-y (relative) - if include_repetitions: - write_uint(buf, 0) # repetition (reuse) - - write_uint(buf, 28) # PROPERTY record - write_byte(buf, 0b0000_1000) # UUUU_VCNS - - write_uint(buf, 15) # XYABSOLUTE record - - # TEXT 5 - write_uint(buf, 19) # TEXT record - var_byte(buf, 0b0101_1011) # 0CNX_YRTL - write_bstring(buf, b'A') # text-string - write_uint(buf, 2) # text-layer - write_uint(buf, 1) # text-datatype - write_sint(buf, 1000) # geometry-x (absolute) - write_sint(buf, 0) # geometry-y (absolute) - if include_repetitions: - write_uint(buf, 0) # repetition (reuse) - - write_uint(buf, 29) # PROPERTY (reuse) - - # PATH 6 - write_uint(buf, 22) # PATH record - var_byte(buf, 0b1111_1011) # EWPX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 10) # half-width - write_byte(buf, 0b0000_1111) # extension-scheme 0000_SSEE - write_sint(buf, 5) # (extension-scheme) - write_sint(buf, -5) # (extension-scheme) - write_uint(buf, 0) # pointlist (1-delta, horiz. first) - write_uint(buf, 3) # (pointlist) dimension - write_sint(buf, 150) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, -50) # (pointlist) - write_sint(buf, 2000) # geometry-x (absolute) - write_sint(buf, 0) # geometry-y (absolute) - if include_repetitions: - write_uint(buf, 0) # repetition (reuse) - - write_uint(buf, 29) # PROPERTY (reuse) - - # POLYGON 7 - write_uint(buf, 21) # POLYGON record - var_byte(buf, 0b0011_1011) # 00PX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 0) # pointlist (1-delta, horiz. first) - write_uint(buf, 4) # (pointlist) dimension - write_sint(buf, 150) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, -50) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, 3000) # geometry-x (absolute) - write_sint(buf, 0) # geometry-y (absolute) - if include_repetitions: - write_uint(buf, 0) # repetition (reuse) - - write_uint(buf, 29) # PROPERTY (reuse) - - write_uint(buf, 28) # PROPERTY record - write_byte(buf, 0b0000_0110) # UUUU_VCNS - write_uint(buf, 1) # propname id - - if variant == 5: - write_uint(buf, 10) # PROPSTRING (explicit id) - write_bstring(buf, b'PropStringId12') - write_uint(buf, 12) # id - - write_uint(buf, 10) # PROPSTRING record (explicit id) - write_bstring(buf, b'Property string value for ID 13') - write_uint(buf, 13) # id - - write_uint(buf, 7) # PROPNAME record (implicit id 0) - write_bstring(buf, b'PROP0') - - write_uint(buf, 7) # PROPNAME record (implicit id 1) - write_bstring(buf, b'PROP1') - buf.write(FOOTER) - return buf - - -def common_geometry_tests(layout: OasisLayout) -> None: - geometry = layout.cells[0].geometry - assert len(geometry) == 8 - - for ii, gg in enumerate(geometry): - msg = f'Failed on element {ii}' - assert gg.x == [0, 0, 0, 0, 0, 1000, 2000, 3000][ii], msg - assert gg.y == [0, 1000, 2000, 3000, 4000, 0, 0, 0][ii], msg - - if ii == 5: - assert gg.layer == 2, msg - assert gg.datatype == 1, msg - else: - assert gg.layer == 1, msg - assert gg.datatype == 2, msg - - if ii < 5: - assert gg.width == 100, msg - assert gg.height == 200, msg - assert geometry[5].string.string == 'A' - assert_equal(geometry[6].point_list, [(150, 0), (0, 50), (-50, 0)]) - assert_equal(geometry[7].point_list, [(150, 0), (0, 50), (-50, 0), - (0, 50), (-100, 0), (0, -100)]) - - -def common_property_tests(layout: OasisLayout) -> None: - geometry = layout.cells[0].geometry - assert len(geometry[0].properties) == 1 - assert geometry[0].properties[0].name.string == 'PROPX' - assert len(geometry[0].properties[0].values) == 0 - - assert len(geometry[1].properties) == 1 - assert geometry[1].properties[0].name == 0 - assert len(geometry[1].properties[0].values) == 1 - assert geometry[1].properties[0].values[0] == -5 - - assert len(geometry[2].properties) == 1 - assert geometry[2].properties[0].name == 0 - assert len(geometry[2].properties[0].values) == 4 - assert geometry[2].properties[0].values[0] == 25 - assert geometry[2].properties[0].values[1] == -124 - assert geometry[2].properties[0].values[2].string == 'PROP_VALUE2' - assert geometry[2].properties[0].values[3].ref == 12 - - for ii in range(3, 8): - msg = f'Failed on element {ii}' - assert geometry[ii].properties[0].name == 0, msg - assert len(geometry[ii].properties[0].values) == 3, msg - assert geometry[ii].properties[0].values[0] == 25, msg - assert geometry[ii].properties[0].values[1] == -124, msg - assert geometry[ii].properties[0].values[2].ref == 13, msg - - for ii in range(3, 7): - msg = f'Failed on element {ii}' - assert len(geometry[ii].properties) == 1, msg - - assert len(geometry[7].properties) == 2 - assert geometry[7].properties[1].name == 1 - - -def test_file_1() -> None: - buf = write_file_common(BytesIO(), 1) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - - assert len(layout.cells) == 1 - assert layout.cells[0].name.string == 'A' - assert not layout.cells[0].properties - common_geometry_tests(layout) - common_property_tests(layout) - - geometry = layout.cells[0].geometry - for ii, gg in enumerate(geometry): - msg = f'Failed on element {ii}' - assert gg.repetition is None, msg - - -def test_file_2() -> None: - buf = write_file_common(BytesIO(), 2) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - - assert len(layout.cells) == 1 - assert layout.cells[0].name.string == 'A' - assert not layout.cells[0].properties - common_geometry_tests(layout) - common_property_tests(layout) - - geometry = layout.cells[0].geometry - for ii, gg in enumerate(geometry): - msg = f'Failed on element {ii}' - assert gg.repetition.a_count == 3, msg - assert gg.repetition.b_count == 2, msg - assert gg.repetition.a_vector == [300, 0], msg - assert gg.repetition.b_vector == [0, 320], msg - - -def test_file_5() -> None: - buf = write_file_common(BytesIO(), 5) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - - assert len(layout.cells) == 1 - assert layout.cells[0].name.string == 'A' - assert not layout.cells[0].properties - common_geometry_tests(layout) - common_property_tests(layout) - - geometry = layout.cells[0].geometry - for ii, gg in enumerate(geometry): - msg = f'Failed on element {ii}' - assert gg.repetition.a_count == 3, msg - assert gg.repetition.b_count == 2, msg - assert gg.repetition.a_vector == [300, 0], msg - assert gg.repetition.b_vector == [0, 320], msg - - -def write_file_3(buf: IO[bytes]) -> IO[bytes]: - """ - """ - buf.write(HEADER) - - write_uint(buf, 10) # PROPSTRING (explicit id) - write_bstring(buf, b'PropStringId12') - write_uint(buf, 12) # id - - write_uint(buf, 10) # PROPSTRING record (explicit id) - write_bstring(buf, b'Property string value for ID 13') - write_uint(buf, 13) # id - - write_uint(buf, 7) # PROPNAME record (implicit id 0) - write_bstring(buf, b'S_GDS_PROPERTY') - - # ** CELL ** - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'A') # Cell name - - write_uint(buf, 16) # XYRELATIVE record - - # RECTANGLE 0 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0111_1011) # SWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 100) # width - write_uint(buf, 200) # height - write_sint(buf, 0) # geometry-x (relative) - write_sint(buf, 1000) # geometry-y (relative) - - write_uint(buf, 28) # PROPERTY record - write_byte(buf, 0b0010_0111) # UUUU_VCNS - write_uint(buf, 0) # propname id - write_uint(buf, 8) # property value 0 (unsigned int) - write_uint(buf, 25) # (...) - write_uint(buf, 10) # property value 1 (a-string) - write_bstring(buf, b'PROP_VALUE2') - - # RECTANGLE 1 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0111_1011) # SWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 100) # width - write_uint(buf, 200) # height - write_sint(buf, 0) # geometry-x (relative) - write_sint(buf, 1000) # geometry-y (relative) - - write_uint(buf, 28) # PROPERTY record - write_byte(buf, 0b1111_0001) # UUUU_VCNS - write_uint(buf, 2) # number of values - write_uint(buf, 8) # property value 0 (unsigned int) - write_uint(buf, 10) # (...) - write_uint(buf, 14) # property value 1 (prop-string ref.) - write_uint(buf, 13) # (...) - - # RECTANGLE 2 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0111_1011) # SWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 100) # width - write_uint(buf, 200) # height - write_sint(buf, 0) # geometry-x (relative) - write_sint(buf, 1000) # geometry-y (relative) - - write_uint(buf, 28) # PROPERTY record - write_byte(buf, 0b0000_1001) # UUUU_VCNS - - # RECTANGLE 3 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0111_1011) # SWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 100) # width - write_uint(buf, 200) # height - write_sint(buf, 0) # geometry-x (relative) - write_sint(buf, 1000) # geometry-y (relative) - - write_uint(buf, 29) # PROPERTY (reuse) - - # RECTANGLE 4 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0111_1011) # SWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 100) # width - write_uint(buf, 200) # height - write_sint(buf, 0) # geometry-x (relative) - write_sint(buf, 1000) # geometry-y (relative) - - write_uint(buf, 28) # PROPERTY record - write_byte(buf, 0b0000_1001) # UUUU_VCNS - - write_uint(buf, 28) # PROPERTY record - write_byte(buf, 0b0010_0111) # UUUU_VCNS - write_uint(buf, 0) # propname id - write_uint(buf, 8) # prop value 0 (unsigned int) - write_uint(buf, 25) # (...) - write_uint(buf, 10) # prop-value 1 (a-string) - write_bstring(buf, b'PROP_VALUE2') # (...) - - write_uint(buf, 15) # XYABSOLUTE record - - # TEXT 5 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0101_1011) # 0CNX_YRTL - write_bstring(buf, b'A') # text-string - write_uint(buf, 2) # text-layer - write_uint(buf, 1) # text-datatype - write_sint(buf, 1000) # geometry-x (absolute) - write_sint(buf, 0) # geometry-y (absolute) - - write_uint(buf, 29) # PROPERTY (reuse) - - # PATH 6 - write_uint(buf, 22) # PATH record - write_byte(buf, 0b1111_1011) # EWPX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 10) # half-width - write_byte(buf, 0b0000_1111) # extension-scheme 0000_SSEE - write_sint(buf, 5) # (extension-scheme) - write_sint(buf, -5) # (extension-scheme) - write_uint(buf, 0) # pointlist (1-delta, horiz. first) - write_uint(buf, 3) # (pointlist) dimension - write_sint(buf, 150) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, -50) # (pointlist) - write_sint(buf, 2000) # geometry-x (absolute) - write_sint(buf, 0) # geometry-y (absolute) - - write_uint(buf, 29) # PROPERTY (reuse) - - # POLYGON 7 - write_uint(buf, 21) # POLYGON record - write_byte(buf, 0b0011_1011) # 00PX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 0) # pointlist (1-delta, horiz. first) - write_uint(buf, 4) # (pointlist) dimension - write_sint(buf, 150) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, -50) # (pointlist) - write_sint(buf, 50) # (pointlist) - write_sint(buf, 3000) # geometry-x (absolute) - write_sint(buf, 0) # geometry-y (absolute) - - write_uint(buf, 29) # PROPERTY (reuse) - - buf.write(FOOTER) - return buf - - -def test_file_3() -> None: - buf = write_file_3(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - - assert len(layout.cells) == 1 - assert layout.cells[0].name.string == 'A' - assert not layout.cells[0].properties - - geometry = layout.cells[0].geometry - assert len(geometry) == 8 - for ii, gg in enumerate(geometry): - msg = f'Failed on element {ii}' - assert gg.x == [0, 0, 0, 0, 0, 1000, 2000, 3000][ii], msg - assert gg.y == [1000, 2000, 3000, 4000, 5000, 0, 0, 0][ii], msg - - if ii == 5: - assert gg.layer == 2, msg - assert gg.datatype == 1, msg - else: - assert gg.layer == 1, msg - assert gg.datatype == 2, msg - - if ii < 5: - assert gg.width == 100, msg - assert gg.height == 200, msg - - assert gg.repetition is None, msg - assert geometry[5].string.string == 'A' - assert_equal(geometry[6].point_list, [(150, 0), (0, 50), (-50, 0)]) - assert_equal(geometry[7].point_list, [(150, 0), (0, 50), (-50, 0), - (0, 50), (-100, 0), (0, -100)]) - - assert len(geometry[0].properties) == 1 - assert geometry[0].properties[0].name == 0 - assert len(geometry[0].properties[0].values) == 2 - assert geometry[0].properties[0].values[0] == 25 - assert geometry[0].properties[0].values[1].string == 'PROP_VALUE2' - - for ii in range(1, 4): - msg = f'Failed on element {ii}' - assert len(geometry[ii].properties) == 1, msg - assert geometry[ii].properties[0].name == 0, msg - assert len(geometry[ii].properties[0].values) == 2, msg - assert geometry[ii].properties[0].values[0] == 10, msg - assert geometry[ii].properties[0].values[1].ref == 13, msg - - assert len(geometry[4].properties) == 2 - assert geometry[4].properties[0].name == 0 - assert geometry[4].properties[1].name == 0 - assert len(geometry[4].properties[0].values) == 2 - assert len(geometry[4].properties[1].values) == 2 - assert geometry[4].properties[0].values[0] == 10 - assert geometry[4].properties[0].values[1].ref == 13 - assert geometry[4].properties[1].values[0] == 25 - assert geometry[4].properties[1].values[1].string == 'PROP_VALUE2' - - for ii in range(5, 8): - msg = f'Failed on element {ii}' - assert len(geometry[ii].properties) == 1, msg - assert geometry[ii].properties[0].name == 0, msg - assert len(geometry[ii].properties[0].values) == 2, msg - assert geometry[ii].properties[0].values[0] == 25, msg - assert geometry[ii].properties[0].values[1].string == 'PROP_VALUE2', msg - - -def write_file_4_6(buf: IO[bytes], variant: int) -> IO[bytes]: - """ - """ - buf.write(HEADER) - - write_uint(buf, 10) # PROPSTRING (explicit id) - write_bstring(buf, b'PropStringId12') - write_uint(buf, 12) # id - - write_uint(buf, 10) # PROPSTRING record (explicit id) - write_bstring(buf, b'Property string value for ID 13') - write_uint(buf, 13) # id - - if variant == 4: - write_uint(buf, 7) # PROPNAME record (implicit id 0) - write_bstring(buf, b'S_GDS_PROPERTY') - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'A') # Cell name - - # RECTANGLE 0 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0111_1011) # SWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 100) # width - write_uint(buf, 200) # height - write_sint(buf, 300) # geometry-x (relative) - write_sint(buf, -400) # geometry-y (relative) - - # ** CELL ** - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'TOP') # Cell name - - # PLACEMENT 0 - write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) - write_byte(buf, 0b1011_0000) # CNXY_RAAF - write_bstring(buf, b'A') # cell name - write_sint(buf, -300) # placement-x - write_sint(buf, 400) # placement-y - - write_uint(buf, 28) # PROPERTY record - write_byte(buf, 0b0010_0111) # UUUU_VCNS - write_uint(buf, 0) # propname-id - write_uint(buf, 8) # prop-value 0 (unsigned int) - write_uint(buf, 25) # (...) - write_uint(buf, 10) # prop-value 1 (a-string) - write_bstring(buf, b'PROP_VALUE2') - - if variant == 6: - write_uint(buf, 28) # PROPERTY record - write_byte(buf, 0b0010_0111) # UUUU_VCNS - write_uint(buf, 0) # propname-id - write_uint(buf, 8) # prop-value 0 (unsigned int) - write_uint(buf, 26) # (...) - write_uint(buf, 10) # prop-value 1 (a-string) - write_bstring(buf, b'PROP_VALUE26') - - # PLACEMENT 1 - write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) - write_byte(buf, 0b0011_0000) # CNXY_RAAF - write_sint(buf, 0) # placement-x - if variant == 4: - write_sint(buf, 200) # placement-y - else: - write_sint(buf, 400) # placement-y - - write_uint(buf, 28) # PROPERTY record - write_byte(buf, 0b1111_0001) # UUUU_VCNS - write_uint(buf, 2) # number of values - write_uint(buf, 8) # prop-value 0 (unsigned int) - write_uint(buf, 10) # (...) - write_uint(buf, 14) # prop-value 1 (prop-string ref.) - write_uint(buf, 13) # (...) - - # PLACEMENT 2 - write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) - write_byte(buf, 0b0001_0000) # CNXY_RAAF - write_sint(buf, 400) # placement-y - - write_uint(buf, 28) # PROPERTY record - write_byte(buf, 0b0000_1001) # UUUU_VCNS - - # PLACEMENT 3 - write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) - write_byte(buf, 0b0010_0000) # CNXY_RAAF - write_sint(buf, 300) # placement-x - - write_uint(buf, 29) # PROPERTY (reuse) - - write_uint(buf, 15) # XYABSOLUTE record - - # PLACEMENT 4 - write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) - write_byte(buf, 0b0011_0001) # CNXY_RAAF - write_sint(buf, 700) # placement-x (absolute) - write_sint(buf, 400) # placement-y (absolute) - - write_uint(buf, 28) # PROPERTY record - write_byte(buf, 0b0000_1001) # UUUU_VCNS - - write_uint(buf, 16) # XYRELATIVE record - - # PLACEMENT 5 - write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) - write_byte(buf, 0b0001_0010) # CNXY_RAAF - write_sint(buf, 1000) # placement-y (relative) - - write_uint(buf, 28) # PROPERTY record - write_byte(buf, 0b0010_0111) # UUUU_VCNS - write_uint(buf, 0) # propname-id - write_uint(buf, 8) # prop-value 0 (unsigned int) - write_uint(buf, 25) # (...) - write_uint(buf, 10) # prop-value 1 (a-string) - write_bstring(buf, b'PROP_VALUE2') - - # PLACEMENT 6 - write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) - write_byte(buf, 0b0001_0011) # CNXY_RAAF - write_sint(buf, 1000) # placement-y (relative) - - write_uint(buf, 29) # PROPERTY (reuse) - - write_uint(buf, 15) # XYABSOLUTE record - - # PLACEMENT 7 - write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) - write_byte(buf, 0b0011_1111) # CNXY_RAAF - write_sint(buf, 2000) # placement-x (absolute) - write_sint(buf, 0) # placement-y (absolute) - write_uint(buf, 1) # repetition (3x4 matrix) - write_uint(buf, 1) # (repetition) x-dimension - write_uint(buf, 2) # (repetition) y-dimension - write_uint(buf, 300) # (repetition) x-spacing - write_uint(buf, 300) # (repetition) y-spacing - - write_uint(buf, 29) # PROPERTY (reuse) - - write_uint(buf, 16) # XYRELATIVE record - - # PLACEMENT 8 - write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) - write_byte(buf, 0b0011_1111) # CNXY_RAAF - write_sint(buf, 2000) # placement-x - write_sint(buf, 0) # placement-y - write_uint(buf, 0) # repetition (reuse) - - write_uint(buf, 29) # PROPERTY (reuse) - - # PLACEMENT 9 - write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) - write_byte(buf, 0b0011_1111) # CNXY_RAAF - write_sint(buf, 2000) # placement-x - write_sint(buf, 0) # placement-y - write_uint(buf, 2) # repetition (3 cols.) - write_uint(buf, 1) # (repetition) dimension - write_uint(buf, 320) # (repetition) offset - - write_uint(buf, 29) # PROPERTY (reuse) - - # PLACEMENT 10 - write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) - write_byte(buf, 0b0011_1111) # CNXY_RAAF - write_sint(buf, 2000) # placement-x - write_sint(buf, 0) # placement-y - write_uint(buf, 3) # repetition (4 rows) - write_uint(buf, 2) # (repetition) dimension - write_uint(buf, 310) # (repetition) offset - - write_uint(buf, 29) # PROPERTY (reuse) - - # PLACEMENT 11 - write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) - write_byte(buf, 0b0011_1111) # CNXY_RAAF - write_sint(buf, 2000) # placement-x - write_sint(buf, 0) # placement-y - write_uint(buf, 4) # repetition (4 arbitrary cols.) - write_uint(buf, 2) # (repetition) dimension - write_uint(buf, 320) # (repetition) - write_uint(buf, 330) # (repetition) - write_uint(buf, 340) # (repetition) - - write_uint(buf, 29) # PROPERTY (reuse) - - # PLACEMENT 12 - write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) - write_byte(buf, 0b0011_1111) # CNXY_RAAF - write_sint(buf, 2000) # placement-x - write_sint(buf, 0) # placement-y - write_uint(buf, 8) # repetition (3x4 matrix, arbitrary vectors) - write_uint(buf, 1) # (repetition) n-dimension - write_uint(buf, 2) # (repetition) m-dimension - write_uint(buf, 310 << 2 | 0b01) # (repetition) n-displacement g-delta (310, 320) - write_sint(buf, 320) - write_uint(buf, 330 << 4 | 0b1010) # (repetition) m-dispalcement g-delta 330/northwest = (-330, 330) - - write_uint(buf, 29) # PROPERTY (reuse) - - if variant == 6: - write_uint(buf, 7) # PROPNAME record (implicit id 0) - write_bstring(buf, b'S_GDS_PROPERTY') - - buf.write(FOOTER) - return buf - - -def test_file_4() -> None: - """ - """ - buf = write_file_4_6(BytesIO(), 4) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - - assert len(layout.cells) == 2 - assert layout.cells[0].name.string == 'A' - assert layout.cells[1].name.string == 'TOP' - assert not layout.cells[0].properties - assert not layout.cells[1].properties - assert not layout.cells[0].placements - assert not layout.cells[1].geometry - - geometry = layout.cells[0].geometry - assert len(geometry) == 1 - assert geometry[0].x == 300 - assert geometry[0].y == -400 - assert geometry[0].layer == 1 - assert geometry[0].datatype == 2 - assert geometry[0].width == 100 - assert geometry[0].height == 200 - - assert layout.propstrings[12].string == 'PropStringId12' - assert layout.propstrings[13].string == 'Property string value for ID 13' - assert layout.propnames[0].string == 'S_GDS_PROPERTY' - - placements = layout.cells[1].placements - assert len(placements) == 13 - for ii, pp in enumerate(placements): - msg = f'Failed on placement {ii}' - assert pp.x == [-300, 0, 0, 300, 700, 700, 700, 2000, 4000, 6000, 8000, 10000, 12000][ii], msg - assert pp.y == [400, 200, 400, 400, 400, 1400, 2400, 0, 0, 0, 0, 0, 0][ii], msg - - if ii == 4 or ii >= 6: - assert pp.flip, msg - else: - assert not pp.flip, msg - - if ii < 7: - assert pp.repetition is None, msg - - assert len(placements[0].properties) == 1 - assert placements[0].properties[0].name == 0 - assert len(placements[0].properties[0].values) == 2 - assert placements[0].properties[0].values[0] == 25 - assert placements[0].properties[0].values[1].string == 'PROP_VALUE2' - - for ii in range(1, 5): - msg = f'Failed on placement {ii}' - assert len(placements[ii].properties) == 1, msg - assert placements[ii].properties[0].name == 0, msg - assert len(placements[ii].properties[0].values) == 2, msg - assert placements[ii].properties[0].values[0] == 10, msg - assert placements[ii].properties[0].values[1].ref == 13, msg - - for ii in range(5, 13): - msg = f'Failed on placement {ii}' - assert len(placements[ii].properties) == 1, msg - assert placements[ii].properties[0].name == 0, msg - assert len(placements[ii].properties[0].values) == 2, msg - assert placements[ii].properties[0].values[0] == 25, msg - assert placements[ii].properties[0].values[1].string == 'PROP_VALUE2', msg - - -def test_file_6() -> None: - """ - """ - buf = write_file_4_6(BytesIO(), 6) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - - assert len(layout.cells) == 2 - assert layout.cells[0].name.string == 'A' - assert layout.cells[1].name.string == 'TOP' - assert not layout.cells[0].properties - assert not layout.cells[1].properties - assert not layout.cells[0].placements - assert not layout.cells[1].geometry - - geometry = layout.cells[0].geometry - assert len(geometry) == 1 - assert geometry[0].x == 300 - assert geometry[0].y == -400 - assert geometry[0].layer == 1 - assert geometry[0].datatype == 2 - assert geometry[0].width == 100 - assert geometry[0].height == 200 - - assert layout.propstrings[12].string == 'PropStringId12' - assert layout.propstrings[13].string == 'Property string value for ID 13' - assert layout.propnames[0].string == 'S_GDS_PROPERTY' - - placements = layout.cells[1].placements - assert len(placements) == 13 - for ii, pp in enumerate(placements): - msg = f'Failed on placement {ii}' - assert pp.x == [-300, 0, 0, 300, 700, 700, 700, 2000, 4000, 6000, 8000, 10000, 12000][ii], msg - assert pp.y == [400, 400, 400, 400, 400, 1400, 2400, 0, 0, 0, 0, 0, 0][ii], msg - - if ii == 4 or ii >= 6: - assert pp.flip, msg - else: - assert not pp.flip, msg - - if ii < 7: - assert pp.repetition is None, msg - - assert len(placements[0].properties) == 2 - assert placements[0].properties[0].name == 0 - assert len(placements[0].properties[0].values) == 2 - assert placements[0].properties[0].values[0] == 25 - assert placements[0].properties[0].values[1].string == 'PROP_VALUE2' - assert placements[0].properties[1].name == 0 - assert len(placements[0].properties[1].values) == 2 - assert placements[0].properties[1].values[0] == 26 - assert placements[0].properties[1].values[1].string == 'PROP_VALUE26' - - for ii in range(1, 5): - msg = f'Failed on placement {ii}' - assert len(placements[ii].properties) == 1, msg - assert placements[ii].properties[0].name == 0, msg - assert len(placements[ii].properties[0].values) == 2, msg - assert placements[ii].properties[0].values[0] == 10, msg - assert placements[ii].properties[0].values[1].ref == 13, msg - - for ii in range(5, 13): - msg = f'Failed on placement {ii}' - assert len(placements[ii].properties) == 1, msg - assert placements[ii].properties[0].name == 0, msg - assert len(placements[ii].properties[0].values) == 2, msg - assert placements[ii].properties[0].values[0] == 25, msg - assert placements[ii].properties[0].values[1].string == 'PROP_VALUE2', msg - - -def write_file_7_8_9(buf: IO[bytes], variant: int) -> IO[bytes]: - """ - """ - buf.write(HEADER) - - write_uint(buf, 28) # PROPERTY record - write_byte(buf, 0b0001_0100) # UUUU_VCNS - write_bstring(buf, b'FileProp1') # property name - write_uint(buf, 10) # prop-value 0 (a-string) - write_bstring(buf, b'FileProp1Value') - - write_uint(buf, 28) # PROPERTY record - write_byte(buf, 0b0001_0110) # UUUU_VCNS - write_uint(buf, 13) # prop-name reference - write_uint(buf, 10) # prop-value 0 (a-string) - write_bstring(buf, b'FileProp1Value') - - write_uint(buf, 8) # PROPNAME record (explicit id) - write_bstring(buf, b'FileProp2') - write_uint(buf, 13) # id - - # associated with PROPNAME? - write_uint(buf, 28) # PROPERTY record - if variant == 8: - # Will give an error since the value modal variable is reset by PROPNAME_ID - write_byte(buf, 0b0001_1110) # UUUU_VCNS - else: - write_byte(buf, 0b0001_0110) # UUUU_VCNS - write_uint(buf, 13) # prop-name reference - if variant != 8: - write_uint(buf, 8) # prop-value 0 (unsigned int) - write_uint(buf, 17) # (...) - - write_uint(buf, 10) # PROPSTRING (explicit id) - write_bstring(buf, b'FileProp2Value') - write_uint(buf, 12) # id - - # associated with PROPSTRING? - write_uint(buf, 28) # PROPERTY record - if variant == 9: - # Will give an error since the value modal variable is unset - write_byte(buf, 0b0001_1110) # UUUU_VCNS - else: - write_byte(buf, 0b0001_0110) # UUUU_VCNS - write_uint(buf, 13) # prop-name reference - if variant != 9: - write_uint(buf, 8) # prop-value 0 (unsigned int) - write_uint(buf, 42) # (...) - - write_uint(buf, 3) # CELLNAME record (implicit id 0) - write_bstring(buf, b'A') - - # associated with cell A, through CELLNAME # TODO - write_uint(buf, 28) # PROPERTY record - write_byte(buf, 0b0001_0100) # UUUU_VCNS - write_bstring(buf, b'CellProp0') # prop name - write_uint(buf, 10) # prop-value 0 (a-string) - write_bstring(buf, b'CPValue0') - - # ** CELL ** - write_uint(buf, 13) # CELL record (name ref.) - write_uint(buf, 0) # Cell name 0 (XYZ) - - # associated with cell A - write_uint(buf, 28) # PROPERTY record - write_byte(buf, 0b0001_0100) # UUUU_VCNS - write_bstring(buf, b'CellProp1') # prop name - write_uint(buf, 10) # prop-value 0 (a-string) - write_bstring(buf, b'CPValue') - - write_uint(buf, 28) # PROPERTY record - write_byte(buf, 0b0001_1100) # UUUU_VCNS - write_bstring(buf, b'CellProp2') # prop name - - # RECTANGLE 0 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0111_1011) # SWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 100) # width - write_uint(buf, 200) # height - write_sint(buf, 300) # geometry-x - write_sint(buf, -400) # geometry-y - - buf.write(FOOTER) - return buf - - -def test_file_7() -> None: - buf = write_file_7_8_9(BytesIO(), 7) - - buf.seek(0) - layout = OasisLayout.read(buf) - - assert len(layout.properties) == 4 - assert layout.properties[0].name.string == 'FileProp1' - assert layout.properties[1].name == 13 - assert layout.properties[2].name == 13 - assert layout.properties[3].name == 13 - assert len(layout.properties[0].values) == 1 - assert len(layout.properties[1].values) == 1 - assert len(layout.properties[2].values) == 1 - assert len(layout.properties[3].values) == 1 - assert layout.properties[1].values[0].string == 'FileProp1Value' - assert layout.properties[1].values[0].string == 'FileProp1Value' - assert layout.properties[2].values[0] == 17 - assert layout.properties[3].values[0] == 42 - - assert len(layout.cells) == 1 - cprops = layout.cells[0].properties - assert len(cprops) == 2 - assert cprops[0].name.string == 'CellProp1' - assert cprops[1].name.string == 'CellProp2' - assert len(cprops[0].values) == 1 - assert len(cprops[1].values) == 1 - assert cprops[0].values[0].string == 'CPValue' - assert cprops[1].values[0].string == 'CPValue' - - assert len(layout.cellnames) == 1 - cnprops = layout.cellnames[0].properties - assert len(cnprops) == 1 - assert cnprops[0].name.string == 'CellProp0' - assert len(cnprops[0].values) == 1 - assert cnprops[0].values[0].string == 'CPValue0' - - # TODO Document that cell properties can be attached to both - # the cell and the cellname - - # TODO Document that value count is ignored when using modal - - -def test_file_8() -> None: - """ - """ - buf = write_file_7_8_9(BytesIO(), 8) - - buf.seek(0) - with pytest.raises(InvalidDataError): - _layout = OasisLayout.read(buf) - - -def test_file_9() -> None: - """ - """ - buf = write_file_7_8_9(BytesIO(), 9) - - buf.seek(0) - with pytest.raises(InvalidDataError): - _layout = OasisLayout.read(buf) diff --git a/fatamorgana/test/test_files_rectangles.py b/fatamorgana/test/test_files_rectangles.py deleted file mode 100644 index 204b9b3..0000000 --- a/fatamorgana/test/test_files_rectangles.py +++ /dev/null @@ -1,273 +0,0 @@ -# mypy: disable-error-code="union-attr" -from typing import IO -from io import BytesIO - -from .utils import HEADER, FOOTER -from ..basic import write_uint, write_sint, write_bstring, write_byte -from ..main import OasisLayout - - -def base_tests(layout: OasisLayout) -> None: - assert layout.version.string == '1.0' - assert layout.unit == 1000 - assert layout.validation.checksum_type == 0 - - assert not layout.properties - assert not layout.xnames - assert not layout.textstrings - assert not layout.cellnames - assert not layout.layers - - assert len(layout.cells) == 1 - assert layout.cells[0].name.string == 'ABC' - assert not layout.cells[0].properties - - geometry = layout.cells[0].geometry - - assert geometry[0].x == 300 - assert geometry[0].y == -400 - assert geometry[1].x == 400 - assert geometry[1].y == -500 - assert geometry[2].x == 600 - assert geometry[2].y == -300 - assert geometry[3].x == 800 - assert geometry[3].y == -300 - - assert geometry[4].y == -600 - assert geometry[5].y == -900 - assert geometry[6].y == -1200 - assert geometry[7].y == -1500 - assert geometry[8].y == -1800 - assert geometry[9].y == 500 - assert geometry[10].y == 2000 - - for ii, gg in enumerate(geometry[3:]): - assert gg.x == 800, f'Failed on rectangle {ii + 3}' - - for ii, gg in enumerate(geometry): - msg = f'Failed on rectangle {ii}' - if ii < 4: - assert gg.layer == 1, msg - assert gg.datatype == 2, msg - else: - assert gg.layer == 2, msg - assert gg.datatype == 3, msg - - if ii < 7: - assert gg.width == 100, msg - assert gg.height == 200, msg - elif ii == 7: - assert gg.width == 150, msg - assert gg.height is None, msg - else: - assert gg.width == 150, msg - assert gg.height == 150, msg - - if ii < 9: - assert gg.repetition is None, msg - - assert geometry[9].repetition.a_count == 3 - assert geometry[9].repetition.b_count == 4 - assert geometry[9].repetition.a_vector == [200, 0] - assert geometry[9].repetition.b_vector == [0, 300] - - assert geometry[10].repetition.x_displacements == [200, 300] - - -def write_file_common(buf: IO[bytes], variant: int) -> IO[bytes]: - """ - """ - assert variant in (1, 2), 'Error in test!!' - - buf.write(HEADER) - - if variant == 2: - write_uint(buf, 7) # PROPNAME record (implict id 0) - write_bstring(buf, b'PROP0') # property name - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'ABC') # Cell name - - # RECTANGLE 0 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0111_1011) # SWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 100) # width - write_uint(buf, 200) # height - write_sint(buf, 300) # geometry-x (absolute) - write_sint(buf, -400) # geometry-y (absolute) - - if variant == 2: - # PROPERTY 0 - write_uint(buf, 28) # PROPERTY record (explicit) - write_byte(buf, 0b0001_0110) # UUUU_VCNS - write_uint(buf, 0) # propname id - write_uint(buf, 2) # property value (real: positive reciprocal) - write_uint(buf, 5) # (real) 1/5 - - write_uint(buf, 16) # XYRELATIVE record - - # RECTANGLE 1 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0111_1011) # SWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 100) # width - write_uint(buf, 200) # height - write_sint(buf, 100) # geometry-x (relative) - write_sint(buf, -100) # geometry-y (relative) - - if variant == 2: - # PROPERTY 1 - write_uint(buf, 29) # PROPERTY record (repeat) - - write_uint(buf, 15) # XYABSOLUTE record - - # RECTANGLE 2 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0111_1011) # SWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 100) # width - write_uint(buf, 200) # height - write_sint(buf, 600) # geometry-x (absolute) - write_sint(buf, -300) # geometry-y (absolute) - - if variant == 2: - # PROPERTY 2 - write_uint(buf, 29) # PROPERTY record (repeat) - - # RECTANGLE 3 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0111_0011) # SWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 100) # width - write_uint(buf, 200) # height - write_sint(buf, 800) # geometry-x (absolute) - - if variant == 2: - # PROPERTY 3 - write_uint(buf, 29) # PROPERTY record (repeat) - - # RECTANGLE 4 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0110_1011) # SWHX_YRDL - write_uint(buf, 2) # layer - write_uint(buf, 3) # datatype - write_uint(buf, 100) # width - write_uint(buf, 200) # height - write_sint(buf, -600) # geometry-y (absolute) - - if variant == 2: - # PROPERTY 4 - write_uint(buf, 29) # PROPERTY record (repeat) - - # RECTANGLE 5 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0110_1000) # SWHX_YRDL - write_uint(buf, 100) # width - write_uint(buf, 200) # height - write_sint(buf, -900) # geometry-y (absolute) - - if variant == 2: - # PROPERTY 5 - write_uint(buf, 29) # PROPERTY record (repeat) - - # RECTANGLE 6 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0000_1000) # SWHX_YRDL - write_sint(buf, -1200) # geometry-y (absolute) - - if variant == 2: - # PROPERTY 6 - write_uint(buf, 29) # PROPERTY record (repeat) - - # RECTANGLE 7 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b1100_1000) # SWHX_YRDL - write_uint(buf, 150) # width - write_sint(buf, -1500) # geometry-y (absolute) - - if variant == 2: - # PROPERTY 7 - write_uint(buf, 29) # PROPERTY record (repeat) - - # RECTANGLE 8 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0000_1000) # SWHX_YRDL - write_sint(buf, -1800) # geometry-y (absolute) - - if variant == 2: - # PROPERTY 8 - write_uint(buf, 29) # PROPERTY record (repeat) - - # RECTANGLE 9 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0000_1100) # SWHX_YRDL - write_sint(buf, 500) # geometry-y (absolute) - write_uint(buf, 1) # repetition (3x4 matrix) - write_uint(buf, 1) # (repetition) x-dimension - write_uint(buf, 2) # (repetition) y-dimension - write_uint(buf, 200) # (repetition) x-spacing - write_uint(buf, 300) # (repetition) y-spacing - - if variant == 2: - # PROPERTY 9 - write_uint(buf, 29) # PROPERTY record (repeat) - - # RECTANGLE 10 - write_uint(buf, 20) # RECTANGLE record - write_byte(buf, 0b0000_1100) # SWHX_YRDL - write_sint(buf, 2000) # geometry-y (absolute) - write_uint(buf, 4) # repetition (3 arbitrary cols.) - write_uint(buf, 1) # (repetition) dimension - write_uint(buf, 200) # (repetition) x-delta - write_uint(buf, 300) # (repetition) x-delta - - if variant == 2: - # PROPERTY 10 - write_uint(buf, 29) # PROPERTY record (repeat) - - buf.write(FOOTER) - return buf - - -def test_file_1() -> None: - buf = write_file_common(BytesIO(), 1) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - assert not layout.propnames - - geometry = layout.cells[0].geometry - for ii, gg in enumerate(geometry): - assert not gg.properties, f'Fail on rectangle {ii}' - - -def test_file_2() -> None: - buf = write_file_common(BytesIO(), 2) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - - assert len(layout.propnames) == 1 - assert layout.propnames[0].string == 'PROP0' - - geometry = layout.cells[0].geometry - - for ii, gg in enumerate(geometry): - msg = f'Failed on rectangle {ii}' - assert len(gg.properties) == 1, msg - prop = gg.properties[0] - - assert prop.name == 0, msg - assert len(prop.values) == 1, msg # type: ignore - assert prop.values[0].numerator == 1, msg # type: ignore - assert prop.values[0].denominator == 5, msg # type: ignore - diff --git a/fatamorgana/test/test_files_texts.py b/fatamorgana/test/test_files_texts.py deleted file mode 100644 index 8980868..0000000 --- a/fatamorgana/test/test_files_texts.py +++ /dev/null @@ -1,735 +0,0 @@ -# mypy: disable-error-code="union-attr, index" -from typing import IO -from io import BytesIO - -import pytest - -from .utils import HEADER, FOOTER -from ..basic import write_uint, write_sint, write_bstring, write_byte -from ..basic import InvalidRecordError, InvalidDataError -from ..basic import GridRepetition, ArbitraryRepetition -from ..main import OasisLayout - - -def base_tests(layout: OasisLayout) -> None: - assert layout.version.string == '1.0' - assert layout.unit == 1000 - assert layout.validation.checksum_type == 0 - - assert not layout.properties - assert not layout.propnames - assert not layout.xnames - assert not layout.cellnames - assert not layout.propstrings - assert not layout.layers - - -def common_tests(layout: OasisLayout) -> None: - assert len(layout.cells) == 1 - assert layout.cells[0].name.string == 'ABC' - - geometry = layout.cells[0].geometry - - assert geometry[0].layer == 1 - assert geometry[0].datatype == 2 - for ii, gg in enumerate(geometry[1:]): - assert gg.layer == 2, f'textstring #{ii + 1}' - assert gg.datatype == 1, f'textstring #{ii + 1}' - - assert geometry[0].x == 100 - assert geometry[0].y == -200 - assert geometry[1].x == 200 - assert geometry[1].y == -400 - assert geometry[2].y == -400 - for ii, gg in enumerate(geometry[2:]): - assert gg.x == 300, f'textstring #{ii + 2}' - - for ii, gg in enumerate(geometry[3:]): - assert gg.y == -300 - 200 * ii, f'textstring #{ii + 3}' - - for ii, gg in enumerate(geometry): - if ii < 4: - assert gg.repetition is None, f'textstring #{ii}' - elif ii in (4, 5, 6, 7, 12, 13, 14, 15): - assert isinstance(gg.repetition, GridRepetition), f'textstring #{ii}' - else: - assert isinstance(gg.repetition, ArbitraryRepetition), f'textstring #{ii}' - - for ii in (4, 5): - assert geometry[ii].repetition.a_count == 3, f'textstring #{ii}' - assert geometry[ii].repetition.b_count == 4, f'textstring #{ii}' - assert geometry[ii].repetition.a_vector == [10, 0], f'textstring #{ii}' - assert geometry[ii].repetition.b_vector == [0, 12], f'textstring #{ii}' - - assert geometry[6].repetition.a_count == 3 - assert geometry[6].repetition.a_vector == [10, 0] - - assert geometry[7].repetition.a_count == 4 - assert geometry[7].repetition.a_vector == [0, 12] - - assert geometry[8].repetition.x_displacements == [12, 13, 14] - assert geometry[9].repetition.x_displacements == [4 * 3, 5 * 3, 6 * 3] - assert geometry[10].repetition.y_displacements == [10, 11] - assert geometry[11].repetition.y_displacements == [2 * 5, 3 * 5] - - assert geometry[12].repetition.a_count == 3 - assert geometry[12].repetition.b_count == 4 - assert geometry[12].repetition.a_vector == [10, 0] - assert geometry[12].repetition.b_vector == [-11, -12] - - assert geometry[13].repetition.a_count == 3 - assert geometry[13].repetition.b_count == 4 - assert geometry[13].repetition.a_vector == [11, 12] - assert geometry[13].repetition.b_vector == [-10, 10] - - assert geometry[14].repetition.a_count == 3 - assert geometry[14].repetition.b_count is None - assert geometry[14].repetition.a_vector == [11, 12] - assert geometry[14].repetition.b_vector is None - - assert geometry[15].repetition.a_count == 4 - assert geometry[15].repetition.b_count is None - assert geometry[15].repetition.a_vector == [-10, 10] - assert geometry[15].repetition.b_vector is None - - assert geometry[17].repetition.x_displacements == [-11, 10] - assert geometry[17].repetition.y_displacements == [12, -10] - - assert geometry[19].repetition.x_displacements == [-12, 9] - assert geometry[19].repetition.y_displacements == [12, -9] - - -def write_file_common(buf: IO[bytes], variant: int) -> IO[bytes]: - """ - Single cell with explicit name 'XYZ' - """ - assert variant in (1, 2, 5, 12), 'Error in test!!' - - buf.write(HEADER) - - if variant == 2: - write_uint(buf, 6) # TEXTSTRING record (explicit id) - write_bstring(buf, b'A') - write_uint(buf, 1) # id - - write_uint(buf, 6) # TEXTSTRING record (explicit id) - write_bstring(buf, b'B') - write_uint(buf, 2) # id - elif variant == 5: - write_uint(buf, 5) # TEXTSTRING record (implicit id 0) - write_bstring(buf, b'A') - - write_uint(buf, 5) # TEXTSTRING record (implicit id 1) - write_bstring(buf, b'B') - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'ABC') # Cell name - - # TEXT 0 - write_uint(buf, 19) # TEXT record - if variant == 1: - write_byte(buf, 0b0101_1011) # 0CNX_YRTL - write_bstring(buf, b'TEXT_ABC') # text string - elif variant in (2, 5, 12): - write_byte(buf, 0b0111_1011) # 0CNX_YRTL - write_uint(buf, 1) # textstring id - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_sint(buf, 100) # x - write_sint(buf, -200) # y - - write_uint(buf, 16) # XYRELATIVE - - # TEXT 1 - write_uint(buf, 19) # TEXT record - if variant == 1: - write_byte(buf, 0b0101_1011) # 0CNX_YRTL - write_bstring(buf, b'TEXT_ABC') # text string - elif variant in (2, 12): - write_byte(buf, 0b0111_1011) # 0CNX_YRTL - write_uint(buf, 2) # textstring id - elif variant == 5: - write_byte(buf, 0b0111_1011) # 0CNX_YRTL - write_uint(buf, 0) # textstring id - write_uint(buf, 2) # layer - write_uint(buf, 1) # datatype - write_sint(buf, 100) # x - write_sint(buf, -200) # y - - write_uint(buf, 15) # XYABSOLUTE - - # TEXT 2 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0001_0000) # 0CNX_YRTL - write_sint(buf, 300) # x - - # TEXT 3 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0000_1000) # 0CNX_YRTL - write_sint(buf, -300) # y - - write_uint(buf, 16) # XYRELATIVE - - # TEXT 4 - write_uint(buf, 19) # TEXT record - if variant == 1: - write_byte(buf, 0b0000_1100) # 0CNX_YRTL - elif variant in (2, 5, 12): - write_byte(buf, 0b0110_1100) # 0CNX_YRTL - write_uint(buf, 1) # textstring id - write_sint(buf, -200) # y - write_uint(buf, 1) # repetition (3x4) - write_uint(buf, 1) # (repetition) - write_uint(buf, 2) # (repetition) - write_uint(buf, 10) # (repetition) - write_uint(buf, 12) # (repetition) - - # TEXT 5 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0000_1100) # 0CNX_YRTL - write_sint(buf, -200) # y - write_uint(buf, 0) # repetition (reuse) - - # TEXT 6 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0000_1100) # 0CNX_YRTL - write_sint(buf, -200) # y - write_uint(buf, 2) # repetition (3 cols.) - write_uint(buf, 1) # (repetition) - write_uint(buf, 10) # (repetition) - - # TEXT 7 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0000_1100) # 0CNX_YRTL - write_sint(buf, -200) # y - write_uint(buf, 3) # repetition (4 cols.) - write_uint(buf, 2) # (repetition) - write_uint(buf, 12) # (repetition) - - # TEXT 8 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0000_1100) # 0CNX_YRTL - write_sint(buf, -200) # y - write_uint(buf, 4) # repetition (4 arbitrary cols.) - write_uint(buf, 2) # (repetition) - write_uint(buf, 12) # (repetition) - write_uint(buf, 13) # (repetition) - write_uint(buf, 14) # (repetition) - - # TEXT 9 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0000_1100) # 0CNX_YRTL - write_sint(buf, -200) # y - write_uint(buf, 5) # repetition (4 arbitrary cols., grid 3) - write_uint(buf, 2) # (repetition) - write_uint(buf, 3) # (repetition) - write_uint(buf, 4) # (repetition) - write_uint(buf, 5) # (repetition) - write_uint(buf, 6) # (repetition) - - # TEXT 10 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0000_1100) # 0CNX_YRTL - write_sint(buf, -200) # y - write_uint(buf, 6) # repetition (4 arbitrary cols., grid 3) - write_uint(buf, 1) # (repetition) - write_uint(buf, 10) # (repetition) - write_uint(buf, 11) # (repetition) - - # TEXT 11 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0000_1100) # 0CNX_YRTL - write_sint(buf, -200) # y - write_uint(buf, 7) # repetition (3 arbitrary cols., grid 5) - write_uint(buf, 1) # (repetition) - write_uint(buf, 5) # (repetition) - write_uint(buf, 2) # (repetition) - write_uint(buf, 3) # (repetition) - - # TEXT 12 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0000_1100) # 0CNX_YRTL - write_sint(buf, -200) # y - write_uint(buf, 8) # repetition (3x4 matrix w/arb. vectors) - write_uint(buf, 1) # (repetition) n-dimension - write_uint(buf, 2) # (repetition) m-dimension - write_uint(buf, (10 << 4) | 0b0000) # (repetition) n-displacement g-delta: 10/east = (10, 0) - write_uint(buf, (11 << 2) | 0b11) # (repetition) m-displacement g-delta: (-11, -12) - write_sint(buf, -12) # (repetition g-delta) - - # TEXT 13 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0000_1100) # 0CNX_YRTL - write_sint(buf, -200) # y - write_uint(buf, 8) # repetition (3x4 matrix w/arb. vectors) - write_uint(buf, 1) # (repetition) n-dimension - write_uint(buf, 2) # (repetition) m-dimension - write_uint(buf, (11 << 2) | 0b01) # (repetition) n-displacement g-delta: (11, 12) - write_sint(buf, 12) - write_uint(buf, (10 << 4) | 0b1010) # (repetition) n-displacement g-delta: 10/northwest = (-10, 10) - - # TEXT 14 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0000_1100) # 0CNX_YRTL - write_sint(buf, -200) # y - write_uint(buf, 9) # repetition (3x arb. vector) - write_uint(buf, 1) # (repetition) dimension - write_uint(buf, (11 << 2) | 0b01) # (repetition) n-displacement g-delta: (11, 12) - write_sint(buf, 12) # (repetition g-delta) - - # TEXT 15 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0000_1100) # 0CNX_YRTL - write_sint(buf, -200) # y - write_uint(buf, 9) # repetition (4x arb. vector) - write_uint(buf, 2) # (repetition) dimension - write_uint(buf, (10 << 4) | 0b1010) # (repetition) n-displacement g-delta: 10/northwest = (-10, 10) - - # TEXT 16 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0000_1100) # 0CNX_YRTL - write_sint(buf, -200) # y - write_uint(buf, 10) # repetition (9x / 8 arb. displacements) - write_uint(buf, 7) # (repetition) dimension - write_uint(buf, (10 << 4) | 0b0000) # (repetition) g-delta: 10/east = (10, 0) - write_uint(buf, (10 << 4) | 0b0010) # (repetition) g-delta: 10/north = (0, 10) - write_uint(buf, (10 << 4) | 0b0100) # (repetition) g-delta: 10/west = (-10, 0) - if variant == 12: - write_uint(buf, (10 << 4) | 0b0110) # (repetition) g-delta: 10/south = (0, -10) - else: - write_uint(buf, (40 << 4) | 0b0110) # (repetition) g-delta: 40/south = (0, -40) - write_uint(buf, (10 << 4) | 0b1000) # (repetition) g-delta: 10/northeast = (10, 10) - write_uint(buf, (10 << 4) | 0b1010) # (repetition) g-delta: 10/northwest = (-10, 10) - write_uint(buf, (10 << 4) | 0b1100) # (repetition) g-delta: 10/southwest = (-10, -10) - if variant == 12: - write_uint(buf, (10 << 4) | 0b1110) # (repetition) g-delta: 20/southeast = (10, -10) - else: - write_uint(buf, (20 << 4) | 0b1110) # (repetition) g-delta: 20/southeast = (20, -20) - - # TEXT 17 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0000_1100) # 0CNX_YRTL - write_sint(buf, -200) # y - write_uint(buf, 10) # repetition (3x / 2 arb. displacements) - write_uint(buf, 1) # (repetition) dimension - write_uint(buf, (11 << 2) | 0b11) # (repetition) g-delta: (-11, 12) - write_sint(buf, 12) # (repetition g-delta) - write_uint(buf, (10 << 4) | 0b1110) # (repetition) n-displacement g-delta: 10/southeast = (10, -10) - - # TEXT 18 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0000_1100) # 0CNX_YRTL - write_sint(buf, -200) # y - write_uint(buf, 11) # repetition (9x / grid 2 / 8 arb. displacements) - write_uint(buf, 7) # (repetition) dimension (9) - write_uint(buf, 2) # (repetition) grid - write_uint(buf, ( 5 << 4) | 0b0000) # (repetition) g-delta: 10/east = (10, 0) - write_uint(buf, ( 5 << 4) | 0b0010) # (repetition) g-delta: 10/north = (0, 10) - write_uint(buf, ( 5 << 4) | 0b0100) # (repetition) g-delta: 10/west = (-10, 0) - if variant == 12: - write_uint(buf, (5 << 4) | 0b0110) # (repetition) g-delta: 10/south = (0, -10) - else: - write_uint(buf, (20 << 4) | 0b0110) # (repetition) g-delta: 40/south = (0, -40) - write_uint(buf, ( 5 << 4) | 0b1000) # (repetition) g-delta: 10/northeast = (10, 10) - write_uint(buf, ( 5 << 4) | 0b1010) # (repetition) g-delta: 10/northwest = (-10, 10) - write_uint(buf, ( 5 << 4) | 0b1100) # (repetition) g-delta: 10/southwest = (-10, -10) - if variant == 12: - write_uint(buf, (5 << 4) | 0b1110) # (repetition) g-delta: 20/southeast = (-10, -10) - else: - write_uint(buf, (10 << 4) | 0b1110) # (repetition) g-delta: 20/southeast = (-20, -20) - - # TEXT 19 - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0000_1100) # 0CNX_YRTL - write_sint(buf, -200) # y - write_uint(buf, 11) # repetition (3x / grid 3 / 2 arb. displacements) - write_uint(buf, 1) # (repetition) dimension - write_uint(buf, 3) # (repetition) grid - write_uint(buf, (4 << 2) | 0b11) # (repetition) g-delta: (-12, 12) - write_sint(buf, 4) # (repetition g-delta) - write_uint(buf, (3 << 4) | 0b1110) # (repetition) n-displacement g-delta: 9/southeast = (9, -9) - - if variant == 12: - write_uint(buf, 6) # TEXTSTRING record (explicit id) - write_bstring(buf, b'A') - write_uint(buf, 1) # id - - write_uint(buf, 6) # TEXTSTRING record (explicit id) - write_bstring(buf, b'B') - write_uint(buf, 2) # id - - buf.write(FOOTER) - return buf - - -def test_file_1() -> None: - buf = write_file_common(BytesIO(), 1) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - common_tests(layout) - - geometry = layout.cells[0].geometry - for ii, gg in enumerate(geometry): - assert gg.string.string == 'TEXT_ABC', f'textstring #{ii}' - - assert geometry[16].repetition.x_displacements == [10, 0, -10, 0, 10, -10, -10, 20] - assert geometry[16].repetition.y_displacements == [0, 10, 0, -40, 10, 10, -10, -20] - - assert geometry[18].repetition.x_displacements == [10, 0, -10, 0, 10, -10, -10, 20] - assert geometry[18].repetition.y_displacements == [0, 10, 0, -40, 10, 10, -10, -20] - - -def test_file_2() -> None: - buf = write_file_common(BytesIO(), 2) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - common_tests(layout) - - geometry = layout.cells[0].geometry - for ii, gg in enumerate(geometry): - if ii in (1, 2, 3): - assert gg.string == 2, f'textstring #{ii}' - else: - assert gg.string == 1, f'textstring #{ii}' - - assert geometry[16].repetition.x_displacements == [10, 0, -10, 0, 10, -10, -10, 20] - assert geometry[16].repetition.y_displacements == [0, 10, 0, -40, 10, 10, -10, -20] - - assert geometry[18].repetition.x_displacements == [10, 0, -10, 0, 10, -10, -10, 20] - assert geometry[18].repetition.y_displacements == [0, 10, 0, -40, 10, 10, -10, -20] - - assert layout.textstrings[1].string == 'A' - assert layout.textstrings[2].string == 'B' - - -def test_file_5() -> None: - buf = write_file_common(BytesIO(), 5) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - common_tests(layout) - - geometry = layout.cells[0].geometry - for ii, gg in enumerate(geometry): - if ii in (1, 2, 3): - assert gg.string == 0, f'textstring #{ii}' - else: - assert gg.string == 1, f'textstring #{ii}' - - assert geometry[16].repetition.x_displacements == [10, 0, -10, 0, 10, -10, -10, 20] - assert geometry[16].repetition.y_displacements == [0, 10, 0, -40, 10, 10, -10, -20] - - assert geometry[18].repetition.x_displacements == [10, 0, -10, 0, 10, -10, -10, 20] - assert geometry[18].repetition.y_displacements == [0, 10, 0, -40, 10, 10, -10, -20] - - assert layout.textstrings[0].string == 'A' - assert layout.textstrings[1].string == 'B' - - -def test_file_12() -> None: - buf = write_file_common(BytesIO(), 12) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - common_tests(layout) - - geometry = layout.cells[0].geometry - for ii, gg in enumerate(geometry): - if ii in (1, 2, 3): - assert gg.string == 2, f'textstring #{ii}' - else: - assert gg.string == 1, f'textstring #{ii}' - - assert geometry[16].repetition.x_displacements == [10, 0, -10, 0, 10, -10, -10, 10] - assert geometry[16].repetition.y_displacements == [0, 10, 0, -10, 10, 10, -10, -10] - - assert geometry[18].repetition.x_displacements == [10, 0, -10, 0, 10, -10, -10, 10] - assert geometry[18].repetition.y_displacements == [0, 10, 0, -10, 10, 10, -10, -10] - - assert layout.textstrings[1].string == 'A' - assert layout.textstrings[2].string == 'B' - - -def write_file_3(buf: IO[bytes]) -> IO[bytes]: - """ - File with one textstring with explicit id, and one with an implicit id. - Should fail. - """ - buf.write(HEADER) - - write_uint(buf, 6) # TEXTSTRING record (explicit id) - write_bstring(buf, b'A') - write_uint(buf, 1) # id - - write_uint(buf, 5) # TEXTSTRING record (implicit id 0) (FAIL due to mix) - write_bstring(buf, b'B') - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'ABC') # Cell name - - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0111_1011) # 0CNX_YRTL - write_uint(buf, 1) # textstring id - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_sint(buf, 100) # x - write_sint(buf, -200) # y - - buf.write(FOOTER) - return buf - - -def test_file_3() -> None: - buf = write_file_3(BytesIO()) - - buf.seek(0) - with pytest.raises(InvalidRecordError): - _layout = OasisLayout.read(buf) - - -def write_file_4(buf: IO[bytes]) -> IO[bytes]: - """ - File with a TEXT record that references a non-existent TEXTSTRING - - TODO add an optional check for valid references - """ - buf.write(HEADER) - - write_uint(buf, 5) # TEXTSTRING record (implicit id 0) - write_bstring(buf, b'A') - - write_uint(buf, 5) # TEXTSTRING record (implicit id 1) - write_bstring(buf, b'B') - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'ABC') # Cell name - - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0111_1011) # 0CNX_YRTL - write_uint(buf, 2) # textstring id # INVALID ID - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_sint(buf, 100) # x - write_sint(buf, -200) # y - - buf.write(FOOTER) - return buf - - -def test_file_4() -> None: - buf = write_file_4(BytesIO()) - - buf.seek(0) -# with pytest.raises(InvalidRecordError): - layout = OasisLayout.read(buf) - - # TODO: check for invalid textstring references - base_tests(layout) - - -def write_file_6(buf: IO[bytes]) -> IO[bytes]: - """ - File with TEXT record that uses an un-filled modal for the repetition - """ - buf.write(HEADER) - - write_uint(buf, 5) # TEXTSTRING record (implicit id 0) - write_bstring(buf, b'A') - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'ABC') # Cell name - - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0111_1111) # 0CNX_YRTL - write_uint(buf, 0) # textstring id - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_sint(buf, 100) # x - write_sint(buf, -200) # y - write_uint(buf, 0) # reuse repetition (FAIL due to empty modal) - - buf.write(FOOTER) - return buf - - -def test_file_6() -> None: - buf = write_file_6(BytesIO()) - - buf.seek(0) - with pytest.raises(InvalidDataError): - _layout = OasisLayout.read(buf) - - -def write_file_7(buf: IO[bytes]) -> IO[bytes]: - """ - File with TEXT record that uses an un-filled modal for the layer - """ - buf.write(HEADER) - - write_uint(buf, 5) # TEXTSTRING record (implicit id 0) - write_bstring(buf, b'A') - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'ABC') # Cell name - - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0111_1010) # 0CNX_YRTL - write_uint(buf, 0) # textstring id - write_uint(buf, 2) # datatype - write_sint(buf, 100) # x - write_sint(buf, -200) # y - - buf.write(FOOTER) - return buf - - -def test_file_7() -> None: - buf = write_file_7(BytesIO()) - - buf.seek(0) - with pytest.raises(InvalidDataError): - _layout = OasisLayout.read(buf) - - -def write_file_8(buf: IO[bytes]) -> IO[bytes]: - """ - File with TEXT record that uses an un-filled modal for the datatype - """ - buf.write(HEADER) - - write_uint(buf, 5) # TEXTSTRING record (implicit id 0) - write_bstring(buf, b'A') - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'ABC') # Cell name - - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0111_1001) # 0CNX_YRTL - write_uint(buf, 0) # textstring id - write_uint(buf, 1) # layer - write_sint(buf, 100) # x - write_sint(buf, -200) # y - - buf.write(FOOTER) - return buf - - -def test_file_8() -> None: - buf = write_file_8(BytesIO()) - - buf.seek(0) - with pytest.raises(InvalidDataError): - _layout = OasisLayout.read(buf) - - -def write_file_9(buf: IO[bytes]) -> IO[bytes]: - """ - File with TEXT record that uses a default modal for the x coordinate - """ - buf.write(HEADER) - - write_uint(buf, 5) # TEXTSTRING record (implicit id 0) - write_bstring(buf, b'A') - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'ABC') # Cell name - - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0110_1011) # 0CNX_YRTL - write_uint(buf, 0) # textstring id - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_sint(buf, -200) # y - - buf.write(FOOTER) - return buf - - -def test_file_9() -> None: - buf = write_file_9(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - text = layout.cells[0].geometry[0] - assert text.x == 0 - assert text.layer == 1 - assert text.datatype == 2 - assert text.y == -200 - - -def write_file_10(buf: IO[bytes]) -> IO[bytes]: - """ - File with TEXT record that uses a default modal for the y coordinate - """ - buf.write(HEADER) - - write_uint(buf, 5) # TEXTSTRING record (implicit id 0) - write_bstring(buf, b'A') - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'ABC') # Cell name - - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0111_0011) # 0CNX_YRTL - write_uint(buf, 0) # textstring id - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_sint(buf, 100) # x - - buf.write(FOOTER) - return buf - - -def test_file_10() -> None: - buf = write_file_10(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - text = layout.cells[0].geometry[0] - assert text.y == 0 - assert text.layer == 1 - assert text.datatype == 2 - assert text.x == 100 - - -def write_file_11(buf: IO[bytes]) -> IO[bytes]: - """ - File with TEXT record that uses an un-filled modal for the text string - """ - buf.write(HEADER) - - write_uint(buf, 5) # TEXTSTRING record (implicit id 0) - write_bstring(buf, b'A') - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'ABC') # Cell name - - write_uint(buf, 19) # TEXT record - write_byte(buf, 0b0001_1011) # 0CNX_YRTL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_sint(buf, 100) # x - write_sint(buf, -200) # y - - buf.write(FOOTER) - return buf - - -def test_file_11() -> None: - buf = write_file_11(BytesIO()) - - buf.seek(0) - with pytest.raises(InvalidDataError): - _layout = OasisLayout.read(buf) diff --git a/fatamorgana/test/test_files_trapezoids.py b/fatamorgana/test/test_files_trapezoids.py deleted file mode 100644 index 262f449..0000000 --- a/fatamorgana/test/test_files_trapezoids.py +++ /dev/null @@ -1,226 +0,0 @@ -# mypy: disable-error-code="union-attr" -from typing import IO -from io import BytesIO - -from .utils import HEADER, FOOTER -from ..basic import write_uint, write_sint, write_bstring, write_byte -from ..main import OasisLayout - - -def base_tests(layout: OasisLayout) -> None: - assert layout.version.string == '1.0' - assert layout.unit == 1000 - assert layout.validation.checksum_type == 0 - - assert not layout.properties - assert not layout.propnames - assert not layout.xnames - assert not layout.textstrings - assert not layout.cellnames - assert not layout.layers - - assert len(layout.cells) == 1 - assert layout.cells[0].name.string == 'ABC' - assert not layout.cells[0].properties - - -def write_file_1(buf: IO[bytes]) -> IO[bytes]: - """ - """ - buf.write(HEADER) - - write_uint(buf, 14) # CELL record (explicit) - write_bstring(buf, b'ABC') # Cell name - - # Trapezoid 0 - write_uint(buf, 23) # TRAPEZOID record - write_byte(buf, 0b0111_1011) # OWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 100) # width - write_uint(buf, 50) # height - write_sint(buf, -20) # delta-a - write_sint(buf, 40) # delta-b - write_sint(buf, 0) # geometry-x (absolute) - write_sint(buf, 100) # geometry-y (absolute) - - write_uint(buf, 16) # XYRELATIVE record - - # Trapezoid 1 - write_uint(buf, 23) # TRAPEZOID record - write_byte(buf, 0b1010_1011) # OWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 50) # height - write_sint(buf, 20) # delta-a - write_sint(buf, 40) # delta-b - write_sint(buf, 300) # geometry-y (absolute) - - # Trapezoid 2 - write_uint(buf, 23) # TRAPEZOID record - write_byte(buf, 0b1100_1001) # OWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 150) # width - write_sint(buf, 20) # delta-a - write_sint(buf, -20) # delta-b - write_sint(buf, 300) # geometry-y (relative) - - # Trapezoid 3 - write_uint(buf, 23) # TRAPEZOID record - write_byte(buf, 0b0100_1101) # OWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 150) # width - write_sint(buf, 20) # delta-a - write_sint(buf, -20) # delta-b - write_sint(buf, 300) # geometry-y (relative) - write_uint(buf, 1) # repetition (3x4 matrix) - write_uint(buf, 1) # (repetition) x-dimension - write_uint(buf, 2) # (repetition) y-dimension - write_uint(buf, 200) # (repetition) x-spacing - write_uint(buf, 300) # (repetition) y-spacing - - write_uint(buf, 15) # XYABSOLUTE record - - # Trapezoid 4 - write_uint(buf, 24) # TRAPEZOID record - write_byte(buf, 0b0111_1011) # OWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 100) # width - write_uint(buf, 50) # height - write_sint(buf, -20) # delta-a - write_sint(buf, 1000) # geometry-x (absolute) - write_sint(buf, 100) # geometry-y (absolute) - - write_uint(buf, 16) # XYRELATIVE record - - # Trapezoid 5 - write_uint(buf, 24) # TRAPEZOID record - write_byte(buf, 0b1010_1011) # OWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 50) # height - write_sint(buf, 20) # delta-a - write_sint(buf, 300) # geometry-y (relative) - - # Trapezoid 6 - write_uint(buf, 24) # TRAPEZOID record - write_byte(buf, 0b1100_1001) # OWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 150) # width - write_sint(buf, 20) # delta-a - write_sint(buf, 300) # geometry-y (relative) - - # Trapezoid 7 - write_uint(buf, 24) # TRAPEZOID record - write_byte(buf, 0b0100_1101) # OWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 150) # width - write_sint(buf, 20) # delta-a - write_sint(buf, 300) # geometry-y (relative) - write_uint(buf, 1) # repetition (3x4 matrix) - write_uint(buf, 1) # (repetition) x-dimension - write_uint(buf, 2) # (repetition) y-dimension - write_uint(buf, 200) # (repetition) x-spacing - write_uint(buf, 300) # (repetition) y-spacing - - write_uint(buf, 15) # XYABSOLUTE record - - # Trapezoid 8 - write_uint(buf, 25) # TRAPEZOID record - write_byte(buf, 0b0111_1011) # OWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 100) # width - write_uint(buf, 50) # height - write_sint(buf, 40) # delta-b - write_sint(buf, 2000) # geometry-x (absolute) - write_sint(buf, 100) # geometry-y (absolute) - - write_uint(buf, 16) # XYRELATIVE record - - # Trapezoid 9 - write_uint(buf, 25) # TRAPEZOID record - write_byte(buf, 0b1010_1011) # OWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 2) # datatype - write_uint(buf, 50) # height - write_sint(buf, 40) # delta-b - write_sint(buf, 300) # geometry-y (relative) - - # Trapezoid 10 - write_uint(buf, 25) # TRAPEZOID record - write_byte(buf, 0b1100_1001) # OWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 150) # width - write_sint(buf, -20) # delta-b - write_sint(buf, 300) # geometry-y (relative) - - # Trapezoid 11 - write_uint(buf, 25) # TRAPEZOID record - write_byte(buf, 0b0100_1101) # OWHX_YRDL - write_uint(buf, 1) # layer - write_uint(buf, 150) # width - write_sint(buf, -20) # delta-b - write_sint(buf, 300) # geometry-y (relative) - write_uint(buf, 1) # repetition (3x4 matrix) - write_uint(buf, 1) # (repetition) x-dimension - write_uint(buf, 2) # (repetition) y-dimension - write_uint(buf, 200) # (repetition) x-spacing - write_uint(buf, 300) # (repetition) y-spacing - - buf.write(FOOTER) - return buf - - -def test_file_1() -> None: - buf = write_file_1(BytesIO()) - - buf.seek(0) - layout = OasisLayout.read(buf) - - base_tests(layout) - - geometry = layout.cells[0].geometry - assert len(geometry) == 12 - - for ii, gg in enumerate(geometry): - msg = f'Failed on trapezoid {ii}' - assert gg.x == 1000 * (ii // 4), msg - assert gg.y == 100 + 300 * (ii % 4), msg - - assert gg.layer == 1, msg - assert gg.datatype == 2, msg - - if ii % 4 == 3: - assert gg.repetition.a_count == 3, msg - assert gg.repetition.b_count == 4, msg - assert gg.repetition.a_vector == [200, 0], msg - assert gg.repetition.b_vector == [0, 300], msg - else: - assert gg.repetition is None, msg - assert not gg.properties, msg - - assert gg.height == 50, msg - if ii % 4 < 2: - assert gg.width == 100, msg - else: - assert gg.width == 150, msg - - if ii in (0, 4): - assert gg.delta_a == -20, msg - elif ii >= 8: - assert gg.delta_a == 0, msg - else: - assert gg.delta_a == 20, msg - - if ii in (0, 1, 8, 9): - assert gg.delta_b == 40, msg - elif 4 <= ii < 8: - assert gg.delta_b == 0, msg - else: - assert gg.delta_b == -20, msg - - assert gg.is_vertical == ((ii % 4) in (1, 2)), msg - - assert not gg.properties diff --git a/fatamorgana/test/test_int.py b/fatamorgana/test/test_int.py deleted file mode 100644 index c974535..0000000 --- a/fatamorgana/test/test_int.py +++ /dev/null @@ -1,68 +0,0 @@ -from itertools import chain -from io import BytesIO - -from ..basic import read_uint, read_sint, write_uint, write_sint - - -uints = ( - ( 0, '00'), - ( 127, '7f'), - ( 128, '80 01'), - (16_383, 'ff 7f'), - (16_384, '80 80 01'), - ) - -uints_readonly = ( - ( 0, '80 80 00'), - ) - -sints = ( - ( 0, '00'), - ( 1, '02'), - ( -1, '03'), - ( 63, '7e'), - ( -64, '81 01'), - ( 8191, 'fe 7f'), - ( -8192, '81 80 01'), - ) - -sints_readonly = ( - ) - - -def test_read_uint() -> None: - buffer = BytesIO(bytes.fromhex( - ''.join([hh for _ii, hh in chain(uints, uints_readonly)]))) - - for ii, _hh in chain(uints, uints_readonly): - assert read_uint(buffer) == ii - - -def test_write_uint() -> None: - buffer = BytesIO() - for ii, _hh in uints: - write_uint(buffer, ii) - - correct_bytes = bytes.fromhex( - ''.join([hh for _ii, hh in uints])) - - assert buffer.getbuffer() == correct_bytes - - -def test_read_sint() -> None: - buffer = BytesIO(bytes.fromhex( - ''.join([hh for _ii, hh in chain(sints, sints_readonly)]))) - - for ii, _hh in chain(sints, sints_readonly): - assert read_sint(buffer) == ii - - -def test_write_sint() -> None: - buffer = BytesIO() - for ii, _hh in sints: - write_sint(buffer, ii) - - correct_bytes = bytes.fromhex( - ''.join([hh for _ii, hh in sints])) - - assert buffer.getbuffer() == correct_bytes diff --git a/fatamorgana/test/utils.py b/fatamorgana/test/utils.py deleted file mode 100644 index 7985908..0000000 --- a/fatamorgana/test/utils.py +++ /dev/null @@ -1,41 +0,0 @@ -from io import BytesIO - -from ..basic import write_uint, write_bstring, write_byte - - -MAGIC_BYTES = b'%SEMI-OASIS\r\n' - - -def _gen_header() -> bytes: - buf = BytesIO() - buf.write(MAGIC_BYTES) - - write_uint(buf, 1) # START record - write_bstring(buf, b'1.0') # version - write_uint(buf, 0) # dbu real type: uint - write_uint(buf, 1000) # dbu value: 1000 per micron - write_uint(buf, 0) # offset table is present here - for _ in range(6): - write_uint(buf, 0) # offset table (0: not strict) - write_uint(buf, 0) # offset table (0: no entry present) - return buf.getvalue() - - -def _gen_footer() -> bytes: - buf = BytesIO() - - write_uint(buf, 2) # END record - - # 254-byte padding, (0-byte bstring with length 0; - # length is written as 0x80 0x80 ... 0x80 0x00) - for _ in range(253): - write_byte(buf, 0x80) - write_byte(buf, 0) - - write_uint(buf, 0) # no validation - return buf.getvalue() - - -HEADER = _gen_header() -FOOTER = _gen_footer() - diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 9c149ed..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,89 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "fatamorgana" -description = "OASIS layout format parser and writer" -readme = "README.md" -license = { file = "LICENSE.md" } -authors = [ - { name="Jan Petykiewicz", email="jan@mpxd.net" }, - ] -homepage = "https://mpxd.net/code/jan/fatamorgana" -repository = "https://mpxd.net/code/jan/fatamorgana" -keywords = [ - "OASIS", - "layout", - "design", - "CAD", - "EDA", - "oas", - "electronics", - "open", - "artwork", - "interchange", - "standard", - "mask", - "pattern", - "IC", - "geometry", - "geometric", - "polygon", - "gds", - ] -classifiers = [ - "Programming Language :: Python :: 3", - "Development Status :: 4 - Beta", - "Intended Audience :: Developers", - "Intended Audience :: Information Technology", - "Intended Audience :: Manufacturing", - "Intended Audience :: Science/Research", - "License :: OSI Approved :: GNU Affero General Public License v3", - "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)", - "Topic :: File Formats", - ] -requires-python = ">=3.11" -dynamic = ["version"] -dependencies = [ - ] - -[tool.hatch.version] -path = "fatamorgana/__init__.py" - -[project.optional-dependencies] -numpy = ["numpy>=1.26"] - - -[tool.ruff] -exclude = [ - ".git", - "dist", - ] -line-length = 145 -indent-width = 4 -lint.dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" -lint.select = [ - "NPY", "E", "F", "W", "B", "ANN", "UP", "SLOT", "SIM", "LOG", - "C4", "ISC", "PIE", "PT", "RET", "TCH", "PTH", "INT", - "ARG", "PL", "R", "TRY", - "G010", "G101", "G201", "G202", - "Q002", "Q003", "Q004", - ] -lint.ignore = [ - #"ANN001", # No annotation - "ANN002", # *args - "ANN003", # **kwargs - "ANN401", # Any - "ANN101", # self: Self - "SIM108", # single-line if / else assignment - "RET504", # x=y+z; return x - "PIE790", # unnecessary pass - "ISC003", # non-implicit string concatenation - "C408", # dict(x=y) instead of {'x': y} - "PLR09", # Too many xxx - "PLR2004", # magic number - "PLC0414", # import x as x - "TRY003", # Long exception message - ] - diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..a8ee066 --- /dev/null +++ b/setup.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 + +from setuptools import setup, find_packages +import fatamorgana + +with open('README.md', 'r') as f: + long_description = f.read() + +setup(name='fatamorgana', + version=fatamorgana.version, + description='OASIS layout format parser and writer', + long_description=long_description, + long_description_content_type='text/markdown', + author='Jan Petykiewicz', + author_email='anewusername@gmail.com', + url='https://mpxd.net/code/jan/fatamorgana', + keywords=[ + 'OASIS', + 'layout', + 'design', + 'CAD', + 'EDA', + 'oas', + 'electronics', + 'open', + 'artwork', + 'interchange', + 'standard', + 'mask', + 'pattern', + 'IC', + 'geometry', + 'geometric', + 'polygon', + 'gds', + ], + classifiers=[ + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Development Status :: 3 - Alpha', + 'Environment :: Other Environment', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'Intended Audience :: Manufacturing', + 'Intended Audience :: Science/Research', + 'License :: OSI Approved :: GNU Affero General Public License v3', + 'Operating System :: POSIX :: Linux', + 'Operating System :: Microsoft :: Windows', + 'Topic :: Scientific/Engineering', + 'Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)', + 'Topic :: Software Development :: Libraries :: Python Modules', + ], + packages=find_packages(), + install_requires=[ + 'typing', + ], + extras_require={ + 'numpy': ['numpy'], + }, + ) +