Compare commits

..

5 Commits
master ... wip

21 changed files with 546 additions and 580 deletions

View File

@ -22,7 +22,7 @@
## Installation ## Installation
**Dependencies:** **Dependencies:**
* python >=3.11 * python >=3.10
* (optional) numpy * (optional) numpy

View File

@ -15,7 +15,7 @@
numpy to speed up reading/writing. numpy to speed up reading/writing.
Dependencies: Dependencies:
- Python 3.11 or later - Python 3.8 or later
- numpy (optional, faster but no additional functionality) - numpy (optional, faster but no additional functionality)
To get started, try: To get started, try:
@ -24,28 +24,17 @@
help(fatamorgana.OasisLayout) help(fatamorgana.OasisLayout)
``` ```
""" """
from .main import ( import pathlib
OasisLayout as OasisLayout,
Cell as Cell, from .main import OasisLayout, Cell, XName
XName as XName,
)
from .basic import ( from .basic import (
NString as NString, NString, AString, Validation, OffsetTable, OffsetEntry,
AString as AString, EOFError, SignedError, InvalidDataError, InvalidRecordError,
Validation as Validation, UnfilledModalError,
OffsetTable as OffsetTable, ReuseRepetition, GridRepetition, ArbitraryRepetition
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,
) )
__author__ = 'Jan Petykiewicz' __author__ = 'Jan Petykiewicz'
__version__ = '0.13' __version__ = '0.12'
version = __version__ version = __version__

View File

@ -2,8 +2,7 @@
This module contains all datatypes and parsing/writing functions for This module contains all datatypes and parsing/writing functions for
all abstractions below the 'record' or 'block' level. all abstractions below the 'record' or 'block' level.
""" """
from typing import Any, IO, Union from typing import Type, Union, Any, Sequence, IO
from collections.abc import Sequence
from fractions import Fraction from fractions import Fraction
from enum import Enum from enum import Enum
import math import math
@ -21,7 +20,7 @@ except ImportError:
''' '''
Type definitions Type definitions
''' '''
real_t = int | float | Fraction real_t = Union[int, float, Fraction]
repetition_t = Union['ReuseRepetition', 'GridRepetition', 'ArbitraryRepetition'] repetition_t = Union['ReuseRepetition', 'GridRepetition', 'ArbitraryRepetition']
property_value_t = Union[int, bytes, 'AString', 'NString', 'PropStringReference', float, Fraction] property_value_t = Union[int, bytes, 'AString', 'NString', 'PropStringReference', float, Fraction]
bytes_t = bytes bytes_t = bytes
@ -185,7 +184,7 @@ if _USE_NUMPY:
byte_arr = _read(stream, 1) byte_arr = _read(stream, 1)
return numpy.unpackbits(numpy.frombuffer(byte_arr, dtype=numpy.uint8)) return numpy.unpackbits(numpy.frombuffer(byte_arr, dtype=numpy.uint8))
def _np_write_bool_byte(stream: IO[bytes], bits: tuple[bool | int, ...]) -> int: def _np_write_bool_byte(stream: IO[bytes], bits: tuple[Union[bool, int], ...]) -> int:
""" """
Pack 8 booleans into a byte, and write it to the stream. Pack 8 booleans into a byte, and write it to the stream.
@ -344,7 +343,7 @@ def read_bstring(stream: IO[bytes]) -> bytes:
return _read(stream, length) return _read(stream, length)
def write_bstring(stream: IO[bytes], bstring: bytes) -> int: def write_bstring(stream: IO[bytes], bstring: bytes):
""" """
Write a binary string to the stream. Write a binary string to the stream.
See `read_bstring()` for format details. See `read_bstring()` for format details.
@ -564,7 +563,7 @@ class NString:
""" """
_string: str _string: str
def __init__(self, string_or_bytes: bytes | str) -> None: def __init__(self, string_or_bytes: Union[bytes, str]) -> None:
""" """
Args: Args:
string_or_bytes: Content of the `NString`. string_or_bytes: Content of the `NString`.
@ -678,7 +677,7 @@ class AString:
""" """
_string: str _string: str
def __init__(self, string_or_bytes: bytes | str) -> None: def __init__(self, string_or_bytes: Union[bytes, str]) -> None:
""" """
Args: Args:
string_or_bytes: Content of the AString. string_or_bytes: Content of the AString.
@ -950,7 +949,7 @@ class OctangularDelta:
sign = self.octangle & 0x02 > 0 sign = self.octangle & 0x02 > 0
xy[axis] = self.proj_mag * (1 - 2 * sign) xy[axis] = self.proj_mag * (1 - 2 * sign)
return xy return xy
else: # noqa: RET505 else:
yn = (self.octangle & 0x02) > 0 yn = (self.octangle & 0x02) > 0
xyn = (self.octangle & 0x01) > 0 xyn = (self.octangle & 0x01) > 0
ys = 1 - 2 * yn ys = 1 - 2 * yn
@ -1097,9 +1096,10 @@ class Delta:
""" """
if self.x == 0 or self.y == 0 or abs(self.x) == abs(self.y): 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) return write_uint(stream, OctangularDelta(self.x, self.y).as_uint() << 1)
size = write_uint(stream, (encode_sint(self.x) << 1) | 0x01) else:
size += write_uint(stream, encode_sint(self.y)) size = write_uint(stream, (encode_sint(self.x) << 1) | 0x01)
return size size += write_uint(stream, encode_sint(self.y))
return size
def __eq__(self, other: Any) -> bool: def __eq__(self, other: Any) -> bool:
return hasattr(other, 'as_list') and self.as_list() == other.as_list() return hasattr(other, 'as_list') and self.as_list() == other.as_list()
@ -1124,11 +1124,12 @@ def read_repetition(stream: IO[bytes]) -> repetition_t:
rtype = read_uint(stream) rtype = read_uint(stream)
if rtype == 0: if rtype == 0:
return ReuseRepetition.read(stream, rtype) 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) 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) return ArbitraryRepetition.read(stream, rtype)
raise InvalidDataError(f'Unexpected repetition type: {rtype}') else:
raise InvalidDataError(f'Unexpected repetition type: {rtype}')
def write_repetition(stream: IO[bytes], repetition: repetition_t) -> int: def write_repetition(stream: IO[bytes], repetition: repetition_t) -> int:
@ -1194,8 +1195,7 @@ class GridRepetition:
a_vector: Sequence[int], a_vector: Sequence[int],
a_count: int, a_count: int,
b_vector: Sequence[int] | None = None, b_vector: Sequence[int] | None = None,
b_count: int | None = None, b_count: int | None = None):
) -> None:
""" """
Args: Args:
a_vector: First lattice vector, of the form `[x, y]`. a_vector: First lattice vector, of the form `[x, y]`.
@ -1221,7 +1221,7 @@ class GridRepetition:
if b_count < 2: if b_count < 2:
b_count = None b_count = None
b_vector = None b_vector = None
warnings.warn('Removed b_count and b_vector since b_count == 1', stacklevel=2) warnings.warn('Removed b_count and b_vector since b_count == 1')
if a_count < 2: if a_count < 2:
raise InvalidDataError(f'Repetition has too-small a_count: {a_count}') raise InvalidDataError(f'Repetition has too-small a_count: {a_count}')
@ -1310,7 +1310,7 @@ class GridRepetition:
size = write_uint(stream, 9) size = write_uint(stream, 9)
size += write_uint(stream, self.a_count - 2) size += write_uint(stream, self.a_count - 2)
size += Delta(*self.a_vector).write(stream) size += Delta(*self.a_vector).write(stream)
else: # noqa: PLR5501 else:
if self.a_vector[1] == 0 and self.b_vector[0] == 0: if self.a_vector[1] == 0 and self.b_vector[0] == 0:
size = write_uint(stream, 1) size = write_uint(stream, 1)
size += write_uint(stream, self.a_count - 2) size += write_uint(stream, self.a_count - 2)
@ -1342,7 +1342,7 @@ class GridRepetition:
return True return True
if self.b_vector is None or other.b_vector is None: if self.b_vector is None or other.b_vector is None:
return False return False
if any(self.b_vector[ii] != other.b_vector[ii] for ii in range(2)): # noqa: SIM103 if any(self.b_vector[ii] != other.b_vector[ii] for ii in range(2)):
return False return False
return True return True
@ -1486,13 +1486,13 @@ class ArbitraryRepetition:
size = write_uint(stream, 10) size = write_uint(stream, 10)
size += write_uint(stream, len(self.x_displacements) - 1) size += write_uint(stream, len(self.x_displacements) - 1)
size += sum(Delta(x, y).write(stream) 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: else:
size = write_uint(stream, 11) size = write_uint(stream, 11)
size += write_uint(stream, len(self.x_displacements) - 1) size += write_uint(stream, len(self.x_displacements) - 1)
size += write_uint(stream, gcd) size += write_uint(stream, gcd)
size += sum(Delta(x // gcd, y // gcd).write(stream) 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 return size
def __eq__(self, other: Any) -> bool: def __eq__(self, other: Any) -> bool:
@ -1580,7 +1580,7 @@ def read_point_list(
assert (dx == 0) or (dy == 0) assert (dx == 0) or (dy == 0)
close_points = [[-dx, -dy]] close_points = [[-dx, -dy]]
elif list_type == 3: elif list_type == 3:
assert 0 in (dx, dy) or dx in (dy, -dy) assert (dx == 0) or (dy == 0) or (dx == dy) or (dx == -dy)
close_points = [[-dx, -dy]] close_points = [[-dx, -dy]]
else: else:
close_points = [[-dx, -dy]] close_points = [[-dx, -dy]]
@ -1636,10 +1636,11 @@ def write_point_list(
h_first = False h_first = False
v_first = False v_first = False
break break
elif point[1] != previous[1] or point[0] == previous[0]: else:
h_first = False if point[1] != previous[1] or point[0] == previous[0]:
v_first = False h_first = False
break v_first = False
break
previous = point previous = point
# If one of h_first or v_first, write a bunch of 1-deltas # If one of h_first or v_first, write a bunch of 1-deltas
@ -1648,7 +1649,7 @@ def write_point_list(
size += write_uint(stream, len(points)) size += write_uint(stream, len(points))
size += sum(write_sint(stream, x + y) for x, y in points) size += sum(write_sint(stream, x + y) for x, y in points)
return size return size
if v_first: elif v_first:
size = write_uint(stream, 1) size = write_uint(stream, 1)
size += write_uint(stream, len(points)) size += write_uint(stream, len(points))
size += sum(write_sint(stream, x + y) for x, y in points) size += sum(write_sint(stream, x + y) for x, y in points)
@ -1721,20 +1722,19 @@ class PropStringReference:
ref: int ref: int
"""ID of the target""" """ID of the target"""
reference_type: type reference_type: Type
"""Type of the target: `bytes`, `NString`, or `AString`""" """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) -> None:
""" """
Args: :param ref: ID number of the target.
ref: ID number of the target. :param ref_type: Type of the target. One of bytes, NString, AString.
ref_type: Type of the target. One of bytes, NString, AString.
""" """
self.ref = ref self.ref = ref
self.ref_type = ref_type self.ref_type = ref_type
def __eq__(self, other: Any) -> bool: def __eq__(self, other: Any) -> bool:
return isinstance(other, type(self)) and self.ref == other.ref and self.reference_type is other.reference_type return isinstance(other, type(self)) and self.ref == other.ref and self.reference_type == other.reference_type
def __repr__(self) -> str: def __repr__(self) -> str:
return f'[{self.ref_type} : {self.ref}]' return f'[{self.ref_type} : {self.ref}]'
@ -1767,33 +1767,34 @@ def read_property_value(stream: IO[bytes]) -> property_value_t:
Raises: Raises:
InvalidDataError: if an invalid type is read. InvalidDataError: if an invalid type is read.
""" """
ref_type: type ref_type: Type
prop_type = read_uint(stream) prop_type = read_uint(stream)
if 0 <= prop_type <= 7: if 0 <= prop_type <= 7:
return read_real(stream, prop_type) return read_real(stream, prop_type)
if prop_type == 8: elif prop_type == 8:
return read_uint(stream) return read_uint(stream)
if prop_type == 9: elif prop_type == 9:
return read_sint(stream) return read_sint(stream)
if prop_type == 10: elif prop_type == 10:
return AString.read(stream) return AString.read(stream)
if prop_type == 11: elif prop_type == 11:
return read_bstring(stream) return read_bstring(stream)
if prop_type == 12: elif prop_type == 12:
return NString.read(stream) return NString.read(stream)
if prop_type == 13: elif prop_type == 13:
ref_type = AString ref_type = AString
ref = read_uint(stream) ref = read_uint(stream)
return PropStringReference(ref, ref_type) return PropStringReference(ref, ref_type)
if prop_type == 14: elif prop_type == 14:
ref_type = bytes ref_type = bytes
ref = read_uint(stream) ref = read_uint(stream)
return PropStringReference(ref, ref_type) return PropStringReference(ref, ref_type)
if prop_type == 15: elif prop_type == 15:
ref_type = NString ref_type = NString
ref = read_uint(stream) ref = read_uint(stream)
return PropStringReference(ref, ref_type) return PropStringReference(ref, ref_type)
raise InvalidDataError(f'Invalid property type: {prop_type}') else:
raise InvalidDataError(f'Invalid property type: {prop_type}')
def write_property_value( def write_property_value(
@ -1829,7 +1830,7 @@ def write_property_value(
else: else:
size = write_uint(stream, 8) size = write_uint(stream, 8)
size += write_uint(stream, value) size += write_uint(stream, value)
elif isinstance(value, Fraction | float | int): elif isinstance(value, (Fraction, float, int)):
size = write_real(stream, value, force_float32) size = write_real(stream, value, force_float32)
elif isinstance(value, AString): elif isinstance(value, AString):
size = write_uint(stream, 10) size = write_uint(stream, 10)
@ -1841,11 +1842,11 @@ def write_property_value(
size = write_uint(stream, 12) size = write_uint(stream, 12)
size += value.write(stream) size += value.write(stream)
elif isinstance(value, PropStringReference): elif isinstance(value, PropStringReference):
if value.ref_type is AString: if value.ref_type == AString:
size = write_uint(stream, 13) size = write_uint(stream, 13)
elif value.ref_type is bytes: elif value.ref_type == bytes:
size = write_uint(stream, 14) 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, 15)
size += write_uint(stream, value.ref) size += write_uint(stream, value.ref)
else: else:
@ -1880,16 +1881,17 @@ def read_interval(stream: IO[bytes]) -> tuple[int | None, int | None]:
interval_type = read_uint(stream) interval_type = read_uint(stream)
if interval_type == 0: if interval_type == 0:
return None, None return None, None
if interval_type == 1: elif interval_type == 1:
return None, read_uint(stream) return None, read_uint(stream)
if interval_type == 2: elif interval_type == 2:
return read_uint(stream), None return read_uint(stream), None
if interval_type == 3: elif interval_type == 3:
v = read_uint(stream) v = read_uint(stream)
return v, v return v, v
if interval_type == 4: elif interval_type == 4:
return read_uint(stream), read_uint(stream) return read_uint(stream), read_uint(stream)
raise InvalidDataError(f'Unrecognized interval type: {interval_type}') else:
raise InvalidDataError(f'Unrecognized interval type: {interval_type}')
def write_interval( def write_interval(
@ -1912,15 +1914,18 @@ def write_interval(
if min_bound is None: if min_bound is None:
if max_bound is None: if max_bound is None:
return write_uint(stream, 0) return write_uint(stream, 0)
return write_uint(stream, 1) + write_uint(stream, max_bound) else:
if max_bound is None: return write_uint(stream, 1) + write_uint(stream, max_bound)
return write_uint(stream, 2) + write_uint(stream, min_bound) else:
if min_bound == max_bound: if max_bound is None:
return write_uint(stream, 3) + write_uint(stream, min_bound) return write_uint(stream, 2) + write_uint(stream, min_bound)
size = write_uint(stream, 4) elif min_bound == max_bound:
size += write_uint(stream, min_bound) return write_uint(stream, 3) + write_uint(stream, min_bound)
size += write_uint(stream, max_bound) else:
return size size = write_uint(stream, 4)
size += write_uint(stream, min_bound)
size += write_uint(stream, max_bound)
return size
class OffsetEntry: class OffsetEntry:
@ -2183,7 +2188,9 @@ class Validation:
checksum_type = read_uint(stream) checksum_type = read_uint(stream)
if checksum_type == 0: if checksum_type == 0:
checksum = None checksum = None
elif checksum_type in (1, 2): elif checksum_type == 1:
checksum = read_u32(stream)
elif checksum_type == 2:
checksum = read_u32(stream) checksum = read_u32(stream)
else: else:
raise InvalidDataError('Invalid validation type!') raise InvalidDataError('Invalid validation type!')
@ -2205,13 +2212,14 @@ class Validation:
""" """
if self.checksum_type == 0: if self.checksum_type == 0:
return write_uint(stream, 0) return write_uint(stream, 0)
if self.checksum is None: elif self.checksum is None:
raise InvalidDataError(f'Checksum is empty but type is {self.checksum_type}') 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) 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) return write_uint(stream, 2) + write_u32(stream, self.checksum)
raise InvalidDataError(f'Unrecognized checksum type: {self.checksum_type}') else:
raise InvalidDataError(f'Unrecognized checksum type: {self.checksum_type}')
def __repr__(self) -> str: def __repr__(self) -> str:
return f'Validation(type: {self.checksum_type} sum: {self.checksum})' return f'Validation(type: {self.checksum_type} sum: {self.checksum})'
@ -2230,7 +2238,7 @@ def write_magic_bytes(stream: IO[bytes]) -> int:
return stream.write(MAGIC_BYTES) return stream.write(MAGIC_BYTES)
def read_magic_bytes(stream: IO[bytes]) -> None: def read_magic_bytes(stream: IO[bytes]):
""" """
Read the magic byte sequence from a stream. Read the magic byte sequence from a stream.
Raise an `InvalidDataError` if it was not found. Raise an `InvalidDataError` if it was not found.

View File

@ -3,7 +3,7 @@ This module contains data structures and functions for reading from and
writing to whole OASIS layout files, and provides a few additional writing to whole OASIS layout files, and provides a few additional
abstractions for the data contained inside them. abstractions for the data contained inside them.
""" """
from typing import IO from typing import Type, IO
import io import io
import logging import logging
@ -163,10 +163,11 @@ class OasisLayout:
""" """
try: try:
record_id = read_uint(stream) record_id = read_uint(stream)
except EOFError: except EOFError as e:
if file_state.within_cblock: if file_state.within_cblock:
return True return True
raise else:
raise e
logger.info(f'read_record of type {record_id} at position 0x{stream.tell():x}') logger.info(f'read_record of type {record_id} at position 0x{stream.tell():x}')
@ -192,7 +193,8 @@ class OasisLayout:
if record_id == 1: if record_id == 1:
if file_state.started: if file_state.started:
raise InvalidRecordError('Duplicate Start record') raise InvalidRecordError('Duplicate Start record')
file_state.started = True else:
file_state.started = True
if record_id == 2 and file_state.within_cblock: if record_id == 2 and file_state.within_cblock:
raise InvalidRecordError('End within CBlock') raise InvalidRecordError('End within CBlock')
@ -202,7 +204,7 @@ class OasisLayout:
file_state.within_cell = False file_state.within_cell = False
elif record_id in range(15, 28) or record_id in (32, 33): elif record_id in range(15, 28) or record_id in (32, 33):
if not file_state.within_cell: if not file_state.within_cell:
raise InvalidRecordError('Geometry outside Cell') raise Exception('Geometry outside Cell')
elif record_id in (13, 14): elif record_id in (13, 14):
file_state.within_cell = True file_state.within_cell = True
else: else:
@ -420,7 +422,7 @@ class Cell:
placements: list[records.Placement] | None = None, placements: list[records.Placement] | None = None,
geometry: list[records.geometry_t] | None = None, geometry: list[records.geometry_t] | None = None,
) -> None: ) -> None:
self.name = name if isinstance(name, NString | int) else NString(name) self.name = name if isinstance(name, (NString, int)) else NString(name)
self.properties = [] if properties is None else properties self.properties = [] if properties is None else properties
self.placements = [] if placements is None else placements self.placements = [] if placements is None else placements
self.geometry = [] if geometry is None else geometry self.geometry = [] if geometry is None else geometry
@ -526,7 +528,7 @@ class XName:
# Mapping from record id to record class. # Mapping from record id to record class.
_GEOMETRY: dict[int, type[records.geometry_t]] = { _GEOMETRY: dict[int, Type[records.geometry_t]] = {
19: records.Text, 19: records.Text,
20: records.Rectangle, 20: records.Rectangle,
21: records.Polygon, 21: records.Polygon,

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,8 @@
""" '''
Build files equivalent to the test cases used by KLayout. Build files equivalent to the test cases used by KLayout.
""" '''
from typing import IO from typing import Callable, IO
from collections.abc import Callable
from pathlib import Path
from . import ( from . import (
@ -17,8 +15,8 @@ from . import (
def build_file(num: str, func: Callable[[IO[bytes]], IO[bytes]]) -> None: def build_file(num: str, func: Callable[[IO[bytes]], IO[bytes]]) -> None:
with Path('t' + num + '.oas').open('wb') as ff: with open('t' + num + '.oas', 'wb') as f:
func(ff) func(f)
def write_all_files() -> None: def write_all_files() -> None:

View File

@ -1,4 +1,4 @@
# mypy: disable-error-code="union-attr" # type: ignore
from typing import IO from typing import IO
from io import BytesIO from io import BytesIO
@ -27,8 +27,8 @@ def base_tests(layout: OasisLayout) -> None:
def write_file_1(buf: IO[bytes]) -> IO[bytes]: def write_file_1(buf: IO[bytes]) -> IO[bytes]:
""" '''
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 14) # CELL record (explicit) write_uint(buf, 14) # CELL record (explicit)

View File

@ -1,4 +1,4 @@
# mypy: disable-error-code="union-attr" # type: ignore
from typing import IO from typing import IO
from io import BytesIO from io import BytesIO
@ -24,9 +24,9 @@ def base_tests(layout: OasisLayout) -> None:
def write_file_1(buf: IO[bytes]) -> IO[bytes]: def write_file_1(buf: IO[bytes]) -> IO[bytes]:
""" '''
Single cell with explicit name 'XYZ' Single cell with explicit name 'XYZ'
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 14) # CELL record (explicit) write_uint(buf, 14) # CELL record (explicit)
@ -49,9 +49,9 @@ def test_file_1() -> None:
def write_file_2(buf: IO[bytes]) -> IO[bytes]: def write_file_2(buf: IO[bytes]) -> IO[bytes]:
""" '''
Two cellnames ('XYZ', 'ABC') and two cells with name references. Two cellnames ('XYZ', 'ABC') and two cells with name references.
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 3) # CELLNAME record (implicit id 0) write_uint(buf, 3) # CELLNAME record (implicit id 0)
@ -86,9 +86,9 @@ def test_file_2() -> None:
def write_file_3(buf: IO[bytes]) -> IO[bytes]: def write_file_3(buf: IO[bytes]) -> IO[bytes]:
""" '''
Invalid file, contains a mix of explicit and implicit cellnames Invalid file, contains a mix of explicit and implicit cellnames
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 4) # CELLNAME record (explicit id) write_uint(buf, 4) # CELLNAME record (explicit id)
@ -113,13 +113,13 @@ def test_file_3() -> None:
buf.seek(0) buf.seek(0)
with pytest.raises(InvalidRecordError): with pytest.raises(InvalidRecordError):
_layout = OasisLayout.read(buf) layout = OasisLayout.read(buf)
def write_file_4(buf: IO[bytes]) -> IO[bytes]: def write_file_4(buf: IO[bytes]) -> IO[bytes]:
""" '''
Two cells referencing two names with explicit ids (unsorted) Two cells referencing two names with explicit ids (unsorted)
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 4) # CELLNAME record (explicit id) write_uint(buf, 4) # CELLNAME record (explicit id)
@ -156,9 +156,9 @@ def test_file_4() -> None:
def write_file_5(buf: IO[bytes]) -> IO[bytes]: def write_file_5(buf: IO[bytes]) -> IO[bytes]:
""" '''
Reference to non-existent cell name. Reference to non-existent cell name.
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 4) # CELLNAME record (explicit id) write_uint(buf, 4) # CELLNAME record (explicit id)
@ -197,9 +197,9 @@ def test_file_5() -> None:
def write_file_6(buf: IO[bytes]) -> IO[bytes]: def write_file_6(buf: IO[bytes]) -> IO[bytes]:
""" '''
Cellname with invalid n-string. Cellname with invalid n-string.
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 4) # CELLNAME record (explicit id) write_uint(buf, 4) # CELLNAME record (explicit id)
@ -226,7 +226,7 @@ def test_file_6() -> None:
buf.seek(0) buf.seek(0)
with pytest.raises(InvalidDataError): with pytest.raises(InvalidDataError):
_layout = OasisLayout.read(buf) layout = OasisLayout.read(buf)
#base_tests(layout) #base_tests(layout)
#assert len(layout.cellnames) == 2 #assert len(layout.cellnames) == 2
@ -238,9 +238,9 @@ def test_file_6() -> None:
def write_file_7(buf: IO[bytes]) -> IO[bytes]: def write_file_7(buf: IO[bytes]) -> IO[bytes]:
""" '''
Unused cellname. Unused cellname.
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 4) # CELLNAME record (explicit id) write_uint(buf, 4) # CELLNAME record (explicit id)

View File

@ -1,4 +1,4 @@
# mypy: disable-error-code="union-attr" # type: ignore
from typing import IO from typing import IO
from io import BytesIO from io import BytesIO
@ -25,8 +25,8 @@ def base_tests(layout: OasisLayout) -> None:
def write_file_1(buf: IO[bytes]) -> IO[bytes]: def write_file_1(buf: IO[bytes]) -> IO[bytes]:
""" '''
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 14) # CELL record (explicit) write_uint(buf, 14) # CELL record (explicit)

View File

@ -1,4 +1,4 @@
# mypy: disable-error-code="union-attr" # type: ignore
from typing import IO from typing import IO
from io import BytesIO from io import BytesIO
@ -21,8 +21,8 @@ def base_tests(layout: OasisLayout) -> None:
def write_file_1(buf: IO[bytes]) -> IO[bytes]: def write_file_1(buf: IO[bytes]) -> IO[bytes]:
""" '''
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 14) # CELL record (explicit) write_uint(buf, 14) # CELL record (explicit)
@ -61,7 +61,7 @@ def write_file_1(buf: IO[bytes]) -> IO[bytes]:
+ [0b11, 0b10] + [0b11, 0b10]
) )
for t, (x, x_en) in enumerate(zip(wh, wh_en, strict=True)): for t, (x, x_en) in enumerate(zip(wh, wh_en)):
write_uint(buf, 26) # CTRAPEZOID record write_uint(buf, 26) # CTRAPEZOID record
write_byte(buf, 0b1000_1011 | (x_en << 5)) # TWHX_YRDL write_byte(buf, 0b1000_1011 | (x_en << 5)) # TWHX_YRDL
write_uint(buf, 1) # layer write_uint(buf, 1) # layer
@ -135,12 +135,13 @@ def test_file_1() -> None:
assert gg.width == [250, None][is_ctrapz], msg assert gg.width == [250, None][is_ctrapz], msg
elif ct_type in range(22, 24) or ct_type == 25: elif ct_type in range(22, 24) or ct_type == 25:
assert gg.height == [100, None][is_ctrapz], msg 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: else:
assert gg.width == 100, msg if ct_type < 8 or 16 <= ct_type < 25 or 26 <= ct_type:
assert gg.height == 250, msg 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: elif ii < 3 and ii % 2:
assert gg.ctrapezoid_type == 24, msg assert gg.ctrapezoid_type == 24, msg
elif ii == 55: elif ii == 55:
@ -153,8 +154,8 @@ def test_file_1() -> None:
def write_file_2(buf: IO[bytes]) -> IO[bytes]: def write_file_2(buf: IO[bytes]) -> IO[bytes]:
""" '''
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 14) # CELL record (explicit) write_uint(buf, 14) # CELL record (explicit)

View File

@ -1,3 +1,4 @@
# type: ignore
from typing import IO from typing import IO
from io import BytesIO from io import BytesIO
import struct import struct
@ -22,11 +23,11 @@ def base_tests(layout: OasisLayout) -> None:
def write_file_1(buf: IO[bytes]) -> IO[bytes]: def write_file_1(buf: IO[bytes]) -> IO[bytes]:
""" '''
File contains one PAD record. File contains one PAD record.
1000 units/micron 1000 units/micron
Offset table inside START. Offset table inside START.
""" '''
buf.write(MAGIC_BYTES) buf.write(MAGIC_BYTES)
write_uint(buf, 1) # START record write_uint(buf, 1) # START record
@ -55,11 +56,11 @@ def test_file_1() -> None:
def write_file_2(buf: IO[bytes]) -> IO[bytes]: def write_file_2(buf: IO[bytes]) -> IO[bytes]:
""" '''
File contains no records. File contains no records.
1/2 unit/micron 1/2 unit/micron
Offset table inside START. Offset table inside START.
""" '''
buf.write(MAGIC_BYTES) buf.write(MAGIC_BYTES)
write_uint(buf, 1) # START record write_uint(buf, 1) # START record
@ -86,11 +87,11 @@ def test_file_2() -> None:
def write_file_3(buf: IO[bytes]) -> IO[bytes]: def write_file_3(buf: IO[bytes]) -> IO[bytes]:
""" '''
File contains no records. File contains no records.
10/4 unit/micron 10/4 unit/micron
Offset table inside START. Offset table inside START.
""" '''
buf.write(MAGIC_BYTES) buf.write(MAGIC_BYTES)
write_uint(buf, 1) # START record write_uint(buf, 1) # START record
@ -118,11 +119,11 @@ def test_file_3() -> None:
def write_file_4(buf: IO[bytes]) -> IO[bytes]: def write_file_4(buf: IO[bytes]) -> IO[bytes]:
""" '''
File contains no records. File contains no records.
12.5 unit/micron (float32) 12.5 unit/micron (float32)
Offset table inside START. Offset table inside START.
""" '''
buf.write(MAGIC_BYTES) buf.write(MAGIC_BYTES)
write_uint(buf, 1) # START record write_uint(buf, 1) # START record
@ -149,11 +150,11 @@ def test_file_4() -> None:
def write_file_5(buf: IO[bytes]) -> IO[bytes]: def write_file_5(buf: IO[bytes]) -> IO[bytes]:
""" '''
File contains no records. File contains no records.
12.5 unit/micron (float64) 12.5 unit/micron (float64)
Offset table inside START. Offset table inside START.
""" '''
buf.write(MAGIC_BYTES) buf.write(MAGIC_BYTES)
write_uint(buf, 1) # START record write_uint(buf, 1) # START record

View File

@ -1,5 +1,5 @@
from typing import IO # type: ignore
from collections.abc import Sequence from typing import Sequence, IO
from io import BytesIO from io import BytesIO
@ -27,7 +27,7 @@ def base_tests(layout: OasisLayout) -> None:
assert not layout.cellnames assert not layout.cellnames
assert len(layout.cells) == 1 assert len(layout.cells) == 1
assert layout.cells[0].name.string == 'A' # type: ignore assert layout.cells[0].name.string == 'A'
assert not layout.cells[0].properties assert not layout.cells[0].properties
@ -207,8 +207,8 @@ def elem_test_text(geometry: Sequence) -> None:
def write_file_1(buf: IO[bytes]) -> IO[bytes]: def write_file_1(buf: IO[bytes]) -> IO[bytes]:
""" '''
""" '''
buf.write(HEADER) buf.write(HEADER)
write_names_geom(buf) write_names_geom(buf)
@ -237,8 +237,8 @@ def test_file_1() -> None:
def write_file_2(buf: IO[bytes]) -> IO[bytes]: def write_file_2(buf: IO[bytes]) -> IO[bytes]:
""" '''
""" '''
buf.write(HEADER) buf.write(HEADER)
write_names_text(buf) write_names_text(buf)
@ -267,8 +267,8 @@ def test_file_2() -> None:
def write_file_3(buf: IO[bytes]) -> IO[bytes]: def write_file_3(buf: IO[bytes]) -> IO[bytes]:
""" '''
""" '''
buf.write(HEADER) buf.write(HEADER)
write_names_text(buf, prefix=b'T') write_names_text(buf, prefix=b'T')
write_names_geom(buf, short=True) write_names_geom(buf, short=True)
@ -283,8 +283,8 @@ def write_file_3(buf: IO[bytes]) -> IO[bytes]:
def write_file_4(buf: IO[bytes]) -> IO[bytes]: def write_file_4(buf: IO[bytes]) -> IO[bytes]:
""" '''
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 14) # CELL record (explicit) write_uint(buf, 14) # CELL record (explicit)

View File

@ -1,4 +1,4 @@
# mypy: disable-error-code="union-attr" # type: ignore
from typing import IO from typing import IO
from io import BytesIO from io import BytesIO
@ -21,8 +21,8 @@ def base_tests(layout: OasisLayout) -> None:
def write_file_1(buf: IO[bytes]) -> IO[bytes]: def write_file_1(buf: IO[bytes]) -> IO[bytes]:
""" '''
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 14) # CELL record (explicit) write_uint(buf, 14) # CELL record (explicit)

View File

@ -1,4 +1,4 @@
# mypy: disable-error-code="union-attr" # type: ignore
from typing import IO from typing import IO
from io import BytesIO from io import BytesIO
@ -27,8 +27,8 @@ def base_tests(layout: OasisLayout) -> None:
def write_file_1(buf: IO[bytes]) -> IO[bytes]: def write_file_1(buf: IO[bytes]) -> IO[bytes]:
""" '''
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 14) # CELL record (explicit) write_uint(buf, 14) # CELL record (explicit)
@ -182,7 +182,7 @@ def test_file_1() -> None:
else: else:
assert gg.half_width == 12, msg assert gg.half_width == 12, msg
assert len(gg.point_list) == 3, msg # type: ignore assert len(gg.point_list) == 3, msg
assert_equal(gg.point_list, [[150, 0], [0, 50], [-50, 0]], err_msg=msg) assert_equal(gg.point_list, [[150, 0], [0, 50], [-50, 0]], err_msg=msg)
if ii >= 4: if ii >= 4:

View File

@ -1,5 +1,5 @@
# mypy: disable-error-code="union-attr" # type: ignore
from typing import IO, cast from typing import Tuple, IO, cast, List
from io import BytesIO from io import BytesIO
from numpy.testing import assert_equal from numpy.testing import assert_equal
@ -22,7 +22,7 @@ def base_tests(layout: OasisLayout) -> None:
assert not layout.layers assert not layout.layers
def write_rectangle(buf: IO[bytes], pos: tuple[int, int] = (300, -400)) -> None: def write_rectangle(buf: IO[bytes], pos: Tuple[int, int] = (300, -400)) -> None:
write_uint(buf, 20) # RECTANGLE record write_uint(buf, 20) # RECTANGLE record
write_byte(buf, 0b0111_1011) # SWHX_YRDL write_byte(buf, 0b0111_1011) # SWHX_YRDL
write_uint(buf, 1) # layer write_uint(buf, 1) # layer
@ -34,8 +34,8 @@ def write_rectangle(buf: IO[bytes], pos: tuple[int, int] = (300, -400)) -> None:
def write_file_1(buf: IO[bytes]) -> IO[bytes]: def write_file_1(buf: IO[bytes]) -> IO[bytes]:
""" '''
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 14) # CELL record (explicit) write_uint(buf, 14) # CELL record (explicit)
@ -174,7 +174,7 @@ def test_file_1() -> None:
assert not layout.cells[1].properties assert not layout.cells[1].properties
assert not layout.cells[1].geometry assert not layout.cells[1].geometry
geometry = cast(list[Rectangle], layout.cells[0].geometry) geometry = cast(List[Rectangle], layout.cells[0].geometry)
assert len(geometry) == 1 assert len(geometry) == 1
assert geometry[0].layer == 1 assert geometry[0].layer == 1
assert geometry[0].datatype == 2 assert geometry[0].datatype == 2
@ -202,7 +202,7 @@ def test_file_1() -> None:
if ii < 3: if ii < 3:
assert pp.y == 400 * (ii + 1), msg assert pp.y == 400 * (ii + 1), msg
elif ii >= 7: elif 7 <= ii:
assert pp.y == 0, msg assert pp.y == 0, msg
if ii < 4 or ii == 5: if ii < 4 or ii == 5:
@ -214,7 +214,7 @@ def test_file_1() -> None:
assert pp.angle == 0, msg assert pp.angle == 0, msg
elif ii in (5, 6): elif ii in (5, 6):
assert pp.angle == 90, msg assert pp.angle == 90, msg
elif ii >= 7: elif 7 <= ii:
assert pp.angle == 270, msg assert pp.angle == 270, msg
if ii < 7: if ii < 7:
@ -250,8 +250,8 @@ def test_file_1() -> None:
def write_file_common(buf: IO[bytes], variant: int) -> IO[bytes]: def write_file_common(buf: IO[bytes], variant: int) -> IO[bytes]:
""" '''
""" '''
assert variant in (2, 3, 5, 7), 'Error in test definition!' assert variant in (2, 3, 5, 7), 'Error in test definition!'
buf.write(HEADER) buf.write(HEADER)
@ -516,8 +516,8 @@ def common_tests(layout: OasisLayout, variant: int) -> None:
def write_file_4(buf: IO[bytes]) -> IO[bytes]: def write_file_4(buf: IO[bytes]) -> IO[bytes]:
""" '''
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 3) # CELLNAME record (implicit id 0) write_uint(buf, 3) # CELLNAME record (implicit id 0)
@ -595,8 +595,8 @@ def write_file_4(buf: IO[bytes]) -> IO[bytes]:
def write_file_6(buf: IO[bytes]) -> IO[bytes]: def write_file_6(buf: IO[bytes]) -> IO[bytes]:
""" '''
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 14) # CELL record (explicit) write_uint(buf, 14) # CELL record (explicit)
@ -748,8 +748,8 @@ def test_file_6() -> None:
def write_file_8(buf: IO[bytes]) -> IO[bytes]: def write_file_8(buf: IO[bytes]) -> IO[bytes]:
""" '''
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 14) # CELL record (explicit) write_uint(buf, 14) # CELL record (explicit)
@ -843,7 +843,7 @@ def test_file_8() -> None:
assert not layout.cells[2].properties assert not layout.cells[2].properties
assert not layout.cells[2].placements assert not layout.cells[2].placements
geometry = cast(list[Rectangle], layout.cells[2].geometry) geometry = cast(List[Rectangle], layout.cells[2].geometry)
assert len(geometry) == 1 assert len(geometry) == 1
assert geometry[0].layer == 1 assert geometry[0].layer == 1
assert geometry[0].datatype == 2 assert geometry[0].datatype == 2

View File

@ -1,4 +1,4 @@
# mypy: disable-error-code="union-attr, arg-type" # type: ignore
from typing import IO from typing import IO
from io import BytesIO from io import BytesIO
@ -107,8 +107,8 @@ def common_tests(layout: OasisLayout) -> None:
def write_file_common(buf: IO[bytes], variant: int) -> IO[bytes]: def write_file_common(buf: IO[bytes], variant: int) -> IO[bytes]:
""" '''
""" '''
assert variant in (1, 3), 'Error in test!!' assert variant in (1, 3), 'Error in test!!'
buf.write(HEADER) buf.write(HEADER)
@ -376,8 +376,8 @@ def test_file_1() -> None:
def write_file_2(buf: IO[bytes]) -> IO[bytes]: def write_file_2(buf: IO[bytes]) -> IO[bytes]:
""" '''
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 14) # CELL record (explicit) write_uint(buf, 14) # CELL record (explicit)
@ -445,7 +445,7 @@ def test_file_3() -> None:
for ii, gg in enumerate(geometry): for ii, gg in enumerate(geometry):
msg = f'Fail on polygon {ii}' msg = f'Fail on polygon {ii}'
assert len(gg.properties) == 1, msg assert len(gg.properties) == 1, msg
assert gg.properties[0].name == 0, msg # type: ignore assert gg.properties[0].name == 0, msg
assert len(gg.properties[0].values) == 1, msg assert len(gg.properties[0].values) == 1, msg
assert gg.properties[0].values[0] * 5 == 1, msg # type: ignore assert gg.properties[0].values[0] * 5 == 1, msg

View File

@ -1,4 +1,4 @@
# mypy: disable-error-code="union-attr, index, arg-type" # type: ignore
from typing import IO from typing import IO
from io import BytesIO from io import BytesIO
@ -24,11 +24,11 @@ def base_tests(layout: OasisLayout) -> None:
def write_file_common(buf: IO[bytes], variant: int) -> IO[bytes]: def write_file_common(buf: IO[bytes], variant: int) -> IO[bytes]:
""" '''
""" '''
include_repetitions = variant in (2, 5) include_repetitions = variant in (2, 5)
def var_byte(buf: IO[bytes], byte: int) -> None: def var_byte(buf, byte):
if include_repetitions: if include_repetitions:
byte |= 0b0100 byte |= 0b0100
write_byte(buf, byte) write_byte(buf, byte)
@ -355,8 +355,8 @@ def test_file_5() -> None:
def write_file_3(buf: IO[bytes]) -> IO[bytes]: def write_file_3(buf: IO[bytes]) -> IO[bytes]:
""" '''
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 10) # PROPSTRING (explicit id) write_uint(buf, 10) # PROPSTRING (explicit id)
@ -580,8 +580,8 @@ def test_file_3() -> None:
def write_file_4_6(buf: IO[bytes], variant: int) -> IO[bytes]: def write_file_4_6(buf: IO[bytes], variant: int) -> IO[bytes]:
""" '''
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 10) # PROPSTRING (explicit id) write_uint(buf, 10) # PROPSTRING (explicit id)
@ -786,8 +786,8 @@ def write_file_4_6(buf: IO[bytes], variant: int) -> IO[bytes]:
def test_file_4() -> None: def test_file_4() -> None:
""" '''
""" '''
buf = write_file_4_6(BytesIO(), 4) buf = write_file_4_6(BytesIO(), 4)
buf.seek(0) buf.seek(0)
@ -823,7 +823,7 @@ def test_file_4() -> None:
assert pp.x == [-300, 0, 0, 300, 700, 700, 700, 2000, 4000, 6000, 8000, 10000, 12000][ii], msg 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 assert pp.y == [400, 200, 400, 400, 400, 1400, 2400, 0, 0, 0, 0, 0, 0][ii], msg
if ii == 4 or ii >= 6: if ii == 4 or 6 <= ii:
assert pp.flip, msg assert pp.flip, msg
else: else:
assert not pp.flip, msg assert not pp.flip, msg
@ -855,8 +855,8 @@ def test_file_4() -> None:
def test_file_6() -> None: def test_file_6() -> None:
""" '''
""" '''
buf = write_file_4_6(BytesIO(), 6) buf = write_file_4_6(BytesIO(), 6)
buf.seek(0) buf.seek(0)
@ -892,7 +892,7 @@ def test_file_6() -> None:
assert pp.x == [-300, 0, 0, 300, 700, 700, 700, 2000, 4000, 6000, 8000, 10000, 12000][ii], msg 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 assert pp.y == [400, 400, 400, 400, 400, 1400, 2400, 0, 0, 0, 0, 0, 0][ii], msg
if ii == 4 or ii >= 6: if ii == 4 or 6 <= ii:
assert pp.flip, msg assert pp.flip, msg
else: else:
assert not pp.flip, msg assert not pp.flip, msg
@ -928,8 +928,8 @@ def test_file_6() -> None:
def write_file_7_8_9(buf: IO[bytes], variant: int) -> IO[bytes]: def write_file_7_8_9(buf: IO[bytes], variant: int) -> IO[bytes]:
""" '''
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 28) # PROPERTY record write_uint(buf, 28) # PROPERTY record
@ -1059,20 +1059,20 @@ def test_file_7() -> None:
def test_file_8() -> None: def test_file_8() -> None:
""" '''
""" '''
buf = write_file_7_8_9(BytesIO(), 8) buf = write_file_7_8_9(BytesIO(), 8)
buf.seek(0) buf.seek(0)
with pytest.raises(InvalidDataError): with pytest.raises(InvalidDataError):
_layout = OasisLayout.read(buf) layout = OasisLayout.read(buf)
def test_file_9() -> None: def test_file_9() -> None:
""" '''
""" '''
buf = write_file_7_8_9(BytesIO(), 9) buf = write_file_7_8_9(BytesIO(), 9)
buf.seek(0) buf.seek(0)
with pytest.raises(InvalidDataError): with pytest.raises(InvalidDataError):
_layout = OasisLayout.read(buf) layout = OasisLayout.read(buf)

View File

@ -1,4 +1,4 @@
# mypy: disable-error-code="union-attr" # type: ignore
from typing import IO from typing import IO
from io import BytesIO from io import BytesIO
@ -75,8 +75,8 @@ def base_tests(layout: OasisLayout) -> None:
def write_file_common(buf: IO[bytes], variant: int) -> IO[bytes]: def write_file_common(buf: IO[bytes], variant: int) -> IO[bytes]:
""" '''
""" '''
assert variant in (1, 2), 'Error in test!!' assert variant in (1, 2), 'Error in test!!'
buf.write(HEADER) buf.write(HEADER)
@ -267,7 +267,7 @@ def test_file_2() -> None:
prop = gg.properties[0] prop = gg.properties[0]
assert prop.name == 0, msg assert prop.name == 0, msg
assert len(prop.values) == 1, msg # type: ignore assert len(prop.values) == 1, msg
assert prop.values[0].numerator == 1, msg # type: ignore assert prop.values[0].numerator == 1, msg
assert prop.values[0].denominator == 5, msg # type: ignore assert prop.values[0].denominator == 5, msg

View File

@ -1,4 +1,4 @@
# mypy: disable-error-code="union-attr, index" # type: ignore
from typing import IO from typing import IO
from io import BytesIO from io import BytesIO
@ -30,8 +30,8 @@ def common_tests(layout: OasisLayout) -> None:
geometry = layout.cells[0].geometry geometry = layout.cells[0].geometry
assert geometry[0].layer == 1 geometry[0].layer == 1
assert geometry[0].datatype == 2 geometry[0].datatype == 2
for ii, gg in enumerate(geometry[1:]): for ii, gg in enumerate(geometry[1:]):
assert gg.layer == 2, f'textstring #{ii + 1}' assert gg.layer == 2, f'textstring #{ii + 1}'
assert gg.datatype == 1, f'textstring #{ii + 1}' assert gg.datatype == 1, f'textstring #{ii + 1}'
@ -100,9 +100,9 @@ def common_tests(layout: OasisLayout) -> None:
def write_file_common(buf: IO[bytes], variant: int) -> IO[bytes]: def write_file_common(buf: IO[bytes], variant: int) -> IO[bytes]:
""" '''
Single cell with explicit name 'XYZ' Single cell with explicit name 'XYZ'
""" '''
assert variant in (1, 2, 5, 12), 'Error in test!!' assert variant in (1, 2, 5, 12), 'Error in test!!'
buf.write(HEADER) buf.write(HEADER)
@ -461,10 +461,10 @@ def test_file_12() -> None:
def write_file_3(buf: IO[bytes]) -> IO[bytes]: def write_file_3(buf: IO[bytes]) -> IO[bytes]:
""" '''
File with one textstring with explicit id, and one with an implicit id. File with one textstring with explicit id, and one with an implicit id.
Should fail. Should fail.
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 6) # TEXTSTRING record (explicit id) write_uint(buf, 6) # TEXTSTRING record (explicit id)
@ -494,15 +494,15 @@ def test_file_3() -> None:
buf.seek(0) buf.seek(0)
with pytest.raises(InvalidRecordError): with pytest.raises(InvalidRecordError):
_layout = OasisLayout.read(buf) layout = OasisLayout.read(buf)
def write_file_4(buf: IO[bytes]) -> IO[bytes]: def write_file_4(buf: IO[bytes]) -> IO[bytes]:
""" '''
File with a TEXT record that references a non-existent TEXTSTRING File with a TEXT record that references a non-existent TEXTSTRING
TODO add an optional check for valid references TODO add an optional check for valid references
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 5) # TEXTSTRING record (implicit id 0) write_uint(buf, 5) # TEXTSTRING record (implicit id 0)
@ -538,9 +538,9 @@ def test_file_4() -> None:
def write_file_6(buf: IO[bytes]) -> IO[bytes]: def write_file_6(buf: IO[bytes]) -> IO[bytes]:
""" '''
File with TEXT record that uses an un-filled modal for the repetition File with TEXT record that uses an un-filled modal for the repetition
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 5) # TEXTSTRING record (implicit id 0) write_uint(buf, 5) # TEXTSTRING record (implicit id 0)
@ -567,13 +567,13 @@ def test_file_6() -> None:
buf.seek(0) buf.seek(0)
with pytest.raises(InvalidDataError): with pytest.raises(InvalidDataError):
_layout = OasisLayout.read(buf) layout = OasisLayout.read(buf)
def write_file_7(buf: IO[bytes]) -> IO[bytes]: def write_file_7(buf: IO[bytes]) -> IO[bytes]:
""" '''
File with TEXT record that uses an un-filled modal for the layer File with TEXT record that uses an un-filled modal for the layer
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 5) # TEXTSTRING record (implicit id 0) write_uint(buf, 5) # TEXTSTRING record (implicit id 0)
@ -598,13 +598,13 @@ def test_file_7() -> None:
buf.seek(0) buf.seek(0)
with pytest.raises(InvalidDataError): with pytest.raises(InvalidDataError):
_layout = OasisLayout.read(buf) layout = OasisLayout.read(buf)
def write_file_8(buf: IO[bytes]) -> IO[bytes]: def write_file_8(buf: IO[bytes]) -> IO[bytes]:
""" '''
File with TEXT record that uses an un-filled modal for the datatype File with TEXT record that uses an un-filled modal for the datatype
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 5) # TEXTSTRING record (implicit id 0) write_uint(buf, 5) # TEXTSTRING record (implicit id 0)
@ -629,13 +629,13 @@ def test_file_8() -> None:
buf.seek(0) buf.seek(0)
with pytest.raises(InvalidDataError): with pytest.raises(InvalidDataError):
_layout = OasisLayout.read(buf) layout = OasisLayout.read(buf)
def write_file_9(buf: IO[bytes]) -> IO[bytes]: def write_file_9(buf: IO[bytes]) -> IO[bytes]:
""" '''
File with TEXT record that uses a default modal for the x coordinate File with TEXT record that uses a default modal for the x coordinate
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 5) # TEXTSTRING record (implicit id 0) write_uint(buf, 5) # TEXTSTRING record (implicit id 0)
@ -669,9 +669,9 @@ def test_file_9() -> None:
def write_file_10(buf: IO[bytes]) -> IO[bytes]: def write_file_10(buf: IO[bytes]) -> IO[bytes]:
""" '''
File with TEXT record that uses a default modal for the y coordinate File with TEXT record that uses a default modal for the y coordinate
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 5) # TEXTSTRING record (implicit id 0) write_uint(buf, 5) # TEXTSTRING record (implicit id 0)
@ -705,9 +705,9 @@ def test_file_10() -> None:
def write_file_11(buf: IO[bytes]) -> IO[bytes]: def write_file_11(buf: IO[bytes]) -> IO[bytes]:
""" '''
File with TEXT record that uses an un-filled modal for the text string File with TEXT record that uses an un-filled modal for the text string
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 5) # TEXTSTRING record (implicit id 0) write_uint(buf, 5) # TEXTSTRING record (implicit id 0)
@ -732,4 +732,4 @@ def test_file_11() -> None:
buf.seek(0) buf.seek(0)
with pytest.raises(InvalidDataError): with pytest.raises(InvalidDataError):
_layout = OasisLayout.read(buf) layout = OasisLayout.read(buf)

View File

@ -1,4 +1,4 @@
# mypy: disable-error-code="union-attr" # type: ignore
from typing import IO from typing import IO
from io import BytesIO from io import BytesIO
@ -25,8 +25,8 @@ def base_tests(layout: OasisLayout) -> None:
def write_file_1(buf: IO[bytes]) -> IO[bytes]: def write_file_1(buf: IO[bytes]) -> IO[bytes]:
""" '''
""" '''
buf.write(HEADER) buf.write(HEADER)
write_uint(buf, 14) # CELL record (explicit) write_uint(buf, 14) # CELL record (explicit)
@ -209,7 +209,7 @@ def test_file_1() -> None:
if ii in (0, 4): if ii in (0, 4):
assert gg.delta_a == -20, msg assert gg.delta_a == -20, msg
elif ii >= 8: elif 8 <= ii:
assert gg.delta_a == 0, msg assert gg.delta_a == 0, msg
else: else:
assert gg.delta_a == 20, msg assert gg.delta_a == 20, msg

View File

@ -44,7 +44,7 @@ classifiers = [
"Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering",
"Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)", "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)",
] ]
requires-python = ">=3.11" requires-python = ">=3.10"
dynamic = ["version"] dynamic = ["version"]
dependencies = [ dependencies = [
] ]
@ -53,38 +53,4 @@ dependencies = [
path = "fatamorgana/__init__.py" path = "fatamorgana/__init__.py"
[project.optional-dependencies] [project.optional-dependencies]
numpy = ["numpy>=1.26"] numpy = ["numpy~=1.21"]
[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
]