Compare commits

..

96 Commits

Author SHA1 Message Date
968392a4a3 bump version number to v0.13
Main changes are numpy 2.0 compatibility and improved type annotations
2024-07-29 18:21:52 -07:00
cc08efcf17 update python version in comment 2024-07-29 18:20:47 -07:00
19324ee147 bump minimum versions and allow numpy 2.0 2024-07-29 18:19:17 -07:00
f4866fb2bb add ruff config 2024-07-29 18:19:17 -07:00
37bac2af0d simplify a comparison 2024-07-29 18:19:17 -07:00
d3ba2ca2af warn at higher stacklevel 2024-07-29 18:19:17 -07:00
3ee1c03f66 improve error handling 2024-07-29 18:19:17 -07:00
1252edb7b5 add missing asserts 2024-07-29 18:19:17 -07:00
8e451c64db improve type annotations 2024-07-29 18:19:17 -07:00
691ce03150 use strict zip 2024-07-29 18:19:17 -07:00
dc9ed8e794 flatten and simplify conditionals 2024-07-29 18:19:17 -07:00
891007054f use pathlibto validate path 2024-07-29 18:19:17 -07:00
5011750637 improve type annotations in tests 2024-07-29 18:19:17 -07:00
f87dc7d771 use ii > 4 instead of 4 < ii 2024-07-29 18:19:17 -07:00
31e52e20d6 use double quotes for docstrings 2024-07-29 18:19:17 -07:00
5ea5e8d8f9 prefix unused variables 2024-07-29 18:19:17 -07:00
d05561af41 comment style 2024-07-29 18:19:17 -07:00
ab8b71b149 fix overflow when using numpy 2.0 2024-07-29 18:19:17 -07:00
c9e894879f use "is" over == for types 2024-07-29 18:19:17 -07:00
d83ef1ce2d update type annotations 2024-07-29 18:19:17 -07:00
bd288d1363 double-up single-letter variable names 2024-07-29 18:19:17 -07:00
931bc599d7 repeat names for re-export 2024-07-29 18:19:17 -07:00
a4c1e52ff8 Modernize type annotations 2024-07-29 18:19:17 -07:00
d61bbc530f Use IO[bytes] everywhere 2024-07-29 18:19:17 -07:00
01b3f9ca3a cleanup based on flake8 output 2024-07-29 18:19:17 -07:00
b2928c8b1c Use IO[bytes] instead of BinaryIO wherever possible 2024-07-29 18:19:17 -07:00
jan
f9d4cfec33 WIP indentation and f-string conversions 2024-07-29 18:18:47 -07:00
7efc5a39d4 bump version to v0.12 2022-08-18 23:44:59 -07:00
9c798b9906 move to hatch-based build 2022-08-18 23:44:20 -07:00
5a42081f6a write "long zero" for END record padding padding 2021-08-11 00:35:14 -07:00
c0e7d11ea2 remove old note 2021-08-11 00:34:37 -07:00
90ea0c2195 fix read_point_list when using numpy 2021-07-30 23:24:58 -07:00
f3be3deadb Add tests 2021-07-30 23:24:45 -07:00
9c5b902a33 ignore missing numpy typing info 2021-07-30 22:47:28 -07:00
fd9f16d705 add implicit_closed arg to read_point_list()
The process for closing a point list depends on the delta type, so it
needs to happen at read time. However, paths aren't implicity closed, so
it needs to be an input option.
2021-07-30 22:47:12 -07:00
48d52f56c7 cast is_vertical to bool 2021-07-30 21:26:48 -07:00
d73e13d13b Allow nonzero value count when using modal (but don't read any values) 2021-07-30 21:26:34 -07:00
a64f2726d0 Don't attempt to decode magic bytes
Since they're wrong, they're likely not decodable anyways.
2021-07-21 01:10:33 -07:00
25fe2067cd Fix error check to not include properties 2021-07-11 17:14:19 -07:00
f8a65968cb use new email 2021-07-11 17:12:54 -07:00
748bb497d1 strip newlines from version string 2021-07-11 17:12:46 -07:00
jan
08ab2b41d5 bump version to v0.11 2021-01-08 09:44:19 -08:00
jan
ba6b04d82d fix version import 2021-01-08 09:43:47 -08:00
f9b8bf823b bump version to v0.10 2020-11-03 01:28:20 -08:00
b996f5f27e use VERSION.py instead of plain VERSION file
reduces reliance on package_data
2020-11-03 01:27:47 -08:00
60f879a1ad style fixes (per flake8) 2020-10-16 19:00:00 -07:00
4a878aa7bf fix type definition for modals 2020-10-16 18:59:45 -07:00
c1b79485a7 Bump version to v0.9 2020-09-10 20:04:15 -07:00
3ca999fa2e documentation updates 2020-09-10 20:03:19 -07:00
3627b63658 Make records store their associated Property records.
Previously, `Property` records were (incorrectly) just associated with
the library or cell.

This requires a change to how `CellName`s are handled by `OasisLayout`,
since they can have associated properties. They now have their own
non-record object (like `XName`s did before) which holds the properties.

There is also now a `FileModals.property_target` attribute which tracks
which record new `Property` rescords should be associated with.
2020-09-10 20:03:01 -07:00
167b16e1c9 fix writing of property values 2020-09-10 19:55:03 -07:00
2c2013a0fc move typing imports to top of file 2020-09-10 19:54:25 -07:00
a80ac6199a Record type 17 (Placement) should not allow modal angle or magnification 2020-09-10 19:54:03 -07:00
jan
97f2bb1238 add docs to gitignore 2020-07-03 13:27:57 -07:00
jan
bc15a66ecc remove extra assignments to *_count and *_vector, and adjust validity checks 2020-07-03 13:27:34 -07:00
jan
5ac774c386 drop OS tags from package 2020-07-03 13:22:29 -07:00
4b7b6b82c1 bump version to v0.8 2020-05-20 21:09:15 -07:00
94555c1b6e Update modal coordinates even if we are in relative mode 2020-05-20 21:07:29 -07:00
86c1e4cd3b bump version to v0.7 2020-05-19 00:52:52 -07:00
fdf5e9f598 enable type checking for downstream 2020-05-19 00:51:40 -07:00
fab80c8517 cosmetic change to code 2020-05-19 00:51:24 -07:00
492d6416db Docstring updates: CTrapezoid info, Polygon+Path point_list description improvement, and better description of where x and y point to 2020-05-19 00:51:10 -07:00
aaef122178 fixup array equality checking in the case where _USE_NUMPY is false but we somehow still get an ndarray
mostly happens during testing
2020-05-19 00:49:42 -07:00
55638fcde5 fix placement rotation (float modulo int was always returning 0??) 2020-05-19 00:48:22 -07:00
99283aaaf0 enable passing in str where an AString or NString is needed. 2020-05-19 00:47:17 -07:00
705926d443 Add UnfilledModalError, records.verify_modal(), and .get_*() methods.
The .get_*() methods are used to verify that we aren't reading from a
pattern with un-filled modals.

The GeometryMixin class was also added here and provides some additional
convenience methods: get_xy() to get an (x,y) tuple and
get_layer_tuple() to get a (layer, datatype) tuple.
2020-05-19 00:42:42 -07:00
e4a62a0f32 fix write_interval for bounded intervals 2020-05-19 00:22:53 -07:00
715fe7ea24 fix non-numpy write_point_list 2020-05-19 00:21:50 -07:00
d25f3f1598 enable str() casts on NString and AString 2020-05-19 00:21:23 -07:00
e909aa958d fix equality for things which may or may not be numpy arrays 2020-05-19 00:20:59 -07:00
411012079d bifurctae read_bool_byte and write_bool_byte into _np_* and _py_* variants 2020-05-19 00:20:38 -07:00
f15499030d only write the first byte -- probably no different but clearer 2020-05-19 00:17:31 -07:00
73310fe993 import repetitions at top level 2020-05-19 00:16:29 -07:00
3a2d889360 add mypy_cache to gitignore 2020-05-17 17:27:11 -07:00
3b01117329 Bump version number to v0.6 2020-04-18 15:55:20 -07:00
4c5f649eec Force mypy to ignore a bunch of simple situations it's not smart enough to reason about 2020-04-18 15:52:36 -07:00
25b2cecec9 Minor changes to satisfy type checker 2020-04-18 15:50:58 -07:00
c9adca61b6 get_pathext may return None 2020-04-18 15:44:44 -07:00
1a259a1c19 Improve ctrapezoid validity checking
width/height might be None; check them against each other only if they
aren't. Also, perform checks after dedup/adjust.
2020-04-18 15:43:25 -07:00
3af40dd1bc Use a dummy unit of -1 when reading (to satisfy type checker) 2020-04-18 15:41:44 -07:00
8fb2f2b594 fix attempted access to xname.string -> xname.bstring 2020-04-18 15:40:35 -07:00
cfb3e90d71 Fix XYmode.absolute 2020-04-18 15:39:27 -07:00
7f0c46525e improve type annotations 2020-04-18 15:38:52 -07:00
e9cf010f54 fix read_u32 2020-04-18 15:35:42 -07:00
06de10062d Additional error checking 2020-04-18 15:35:29 -07:00
62fca39a69 Modernize comments and type annotations. Ugly commit with a couple code fixes:
- modal variable name change: path_halfwidth -> path_half_width
- Fixed `hasattr(other, 'as_list')` calls in __eq__() functions
2020-04-18 03:08:08 -07:00
6f2200c5ed Faster/simpler cumsum approach in read_point_list
Reqires a special case for ndarrays in dedup_field() -- probably a good
idea anyways if user gives us an ndarray
2020-04-18 03:00:36 -07:00
e046af8ce8 Handle more error cases 2020-04-18 02:54:46 -07:00
533c85b249 Fix conditional for writing real-typed properties 2020-04-18 01:42:14 -07:00
bb9ebfc8f9 Generate a warning instead of printing 2020-04-18 01:37:53 -07:00
f4eeb50a6f Fix incorrect calls to .write() 2020-04-18 01:31:36 -07:00
58b4f4a40f Some minor docstring/readme updates 2020-04-17 13:51:43 -07:00
b5a7c9a7ad fix non-numpy read_bool_byte 2020-04-16 22:41:53 -07:00
deb0fe3bef Bump version to 0.5 2019-09-28 11:23:12 -07:00
e0c2947865 trim down classifiers 2019-09-28 11:22:43 -07:00
e514ade2b1 Use fatamorgana/VERSION file to single-source version number
`import fatamorgana` inside setup.py could break if dependencies weren't
satisfied
2019-09-28 11:22:27 -07:00
31 changed files with 8494 additions and 2152 deletions

30
.flake8 Normal file
View File

@ -0,0 +1,30 @@
[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,

7
.gitignore vendored
View File

@ -1,7 +1,14 @@
*.pyc *.pyc
__pycache__ __pycache__
*.idea *.idea
.mypy_cache/
build build
dist dist
fatamorgana.egg-info fatamorgana.egg-info
docs
*.gds
*.gds.gz
*.oas
*.oas.gz

View File

@ -1,2 +0,0 @@
include README.md
include LICENSE.md

View File

@ -3,6 +3,8 @@
**fatamorgana** is a Python package for reading and writing OASIS format layout files. **fatamorgana** is a Python package for reading and writing OASIS format layout files.
**Homepage:** https://mpxd.net/code/jan/fatamorgana **Homepage:** https://mpxd.net/code/jan/fatamorgana
* [PyPI](https://pypi.org/project/fatamorgana)
* [Github mirror](https://github.com/anewusername/fatamorgana)
**Capabilities:** **Capabilities:**
* This package is a work-in-progress and is largely untested -- it works for * This package is a work-in-progress and is largely untested -- it works for
@ -20,18 +22,18 @@
## Installation ## Installation
**Dependencies:** **Dependencies:**
* python 3.5 or newer * python >=3.11
* (optional) numpy * (optional) numpy
Install with pip from PyPi (preferred): Install with pip from PyPi (preferred):
```bash ```bash
pip install fatamorgana pip3 install fatamorgana
``` ```
Install directly from git repository: Install directly from git repository:
```bash ```bash
pip install git+https://mpxd.net/code/jan/fatamorgana.git@release pip3 install git+https://mpxd.net/code/jan/fatamorgana.git@release
``` ```
## Documentation ## Documentation

1
fatamorgana/LICENSE.md Symbolic link
View File

@ -0,0 +1 @@
../LICENSE.md

1
fatamorgana/README.md Symbolic link
View File

@ -0,0 +1 @@
../README.md

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -3,18 +3,22 @@ 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
import io import io
import logging import logging
from . import records from . import records
from .records import Modals from .records import Modals, Record
from .basic import OffsetEntry, OffsetTable, NString, AString, real_t, Validation, \ from .basic import (
read_magic_bytes, write_magic_bytes, read_uint, EOFError, \ OffsetEntry, OffsetTable, NString, AString, real_t, Validation,
InvalidDataError, InvalidRecordError read_magic_bytes, write_magic_bytes, read_uint, EOFError,
InvalidRecordError,
)
__author__ = 'Jan Petykiewicz' __author__ = 'Jan Petykiewicz'
#logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -23,17 +27,21 @@ class FileModals:
""" """
File-scoped modal variables File-scoped modal variables
""" """
cellname_implicit = None # type: bool or None cellname_implicit: bool | None = None
propname_implicit = None # type: bool or None propname_implicit: bool | None = None
xname_implicit = None # type: bool or None xname_implicit: bool | None = None
textstring_implicit = None # type: bool or None textstring_implicit: bool | None = None
propstring_implicit = None # type: bool or None propstring_implicit: bool | None = None
cellname_implicit = None # type: bool or None
within_cell = False # type: bool property_target: list[records.Property]
within_cblock = False # type: bool
end_has_offset_table = None # type: bool within_cell: bool = False
started = False # type: bool 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
class OasisLayout: class OasisLayout:
@ -43,49 +51,56 @@ class OasisLayout:
Names and strings are stored in dicts, indexed by reference number. Names and strings are stored in dicts, indexed by reference number.
Layer names and properties are stored directly using their associated Layer names and properties are stored directly using their associated
record objects. record objects.
Cells are stored using Cell objects (different from Cell record objects). Cells are stored using `Cell` objects (different from `records.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]
""" """
version = None # type: AString # File properties
unit = None # type: real_t version: AString
validation = None # type: Validation """File format version string ('1.0')"""
properties = None # type: List[records.Property] unit: real_t
cells = None # type: List[Cell] """grid steps per micron"""
cellnames = None # type: Dict[int, NString] validation: Validation
propnames = None # type: Dict[int, NString] """checksum data"""
xnames = None # type: Dict[int, XName]
textstrings = None # type: Dict[int, AString] # Data
propstrings = None # type: Dict[int, AString] properties: list[records.Property]
layers = None # type: List[records.LayerName] """Property values"""
cells: list['Cell']
"""Layout cells"""
def __init__(self, unit: real_t, validation: Validation = None): 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:
""" """
:param unit: Real number (i.e. int, float, or Fraction), grid steps per micron. Args:
:param validation: Validation object containing checksum data. unit: Real number (i.e. int, float, or `Fraction`), grid steps per micron.
Default creates a Validation object of the "no checksum" type. validation: `Validation` object containing checksum data.
Default creates a `Validation` object of the "no checksum" type.
""" """
if validation is None: if validation is None:
validation = Validation(0) validation = Validation(0)
@ -103,16 +118,19 @@ class OasisLayout:
self.layers = [] self.layers = []
@staticmethod @staticmethod
def read(stream: io.BufferedIOBase) -> 'OasisLayout': def read(stream: IO[bytes]) -> 'OasisLayout':
""" """
Read an entire .oas file into an OasisLayout object. Read an entire .oas file into an `OasisLayout` object.
:param stream: Stream to read from. Args:
:return: New OasisLayout object. stream: Stream to read from.
Returns:
New `OasisLayout` object.
""" """
file_state = FileModals() layout = OasisLayout(unit=-1) # dummy unit
modals = Modals() modals = Modals()
layout = OasisLayout(unit=None) file_state = FileModals(layout.properties)
read_magic_bytes(stream) read_magic_bytes(stream)
@ -120,32 +138,39 @@ class OasisLayout:
pass pass
return layout return layout
def read_record(self, def read_record(
stream: io.BufferedIOBase, self,
modals: Modals, stream: IO[bytes],
file_state: FileModals modals: Modals,
) -> bool: file_state: FileModals
) -> bool:
""" """
Read a single record of unspecified type from a stream, adding its Read a single record of unspecified type from a stream, adding its
contents into this OasisLayout object. contents into this `OasisLayout` object.
:param stream: Stream to read from. Args:
:param modals: Modal variable data, used to fill unfilled record stream: Stream to read from.
fields and updated using filled record fields. modals: Modal variable data, used to fill unfilled record
:param file_state: File status data. fields and updated using filled record fields.
:return: True if EOF was reached without error, False otherwise. file_state: File status data.
:raises: InvalidRecordError from unexpected records;
InvalidDataError from within record parsers. Returns:
`True` if EOF was reached without error, `False` otherwise.
Raises:
InvalidRecordError: from unexpected records
InvalidDataError: from within record parsers
""" """
try: try:
record_id = read_uint(stream) record_id = read_uint(stream)
except EOFError as e: except EOFError:
if file_state.within_cblock: if file_state.within_cblock:
return True return True
else: raise
raise e
logger.info('read_record of type {} at position 0x{:x}'.format(record_id, stream.tell())) logger.info(f'read_record of type {record_id} at position 0x{stream.tell():x}')
record: Record
# CBlock # CBlock
if record_id == 34: if record_id == 34:
@ -163,12 +188,11 @@ class OasisLayout:
# Make sure order is valid (eg, no out-of-cell geometry) # Make sure order is valid (eg, no out-of-cell geometry)
if not file_state.started and record_id != 1: if not file_state.started and record_id != 1:
raise InvalidRecordError('Non-Start record {} before Start'.format(record_id)) raise InvalidRecordError(f'Non-Start record {record_id} before Start')
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')
else: file_state.started = True
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')
@ -176,25 +200,28 @@ class OasisLayout:
pass pass
elif record_id in range(3, 13) or record_id in (28, 29): elif record_id in range(3, 13) or record_id in (28, 29):
file_state.within_cell = False file_state.within_cell = False
elif record_id in range(15, 29) 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 Exception('Geometry outside Cell') raise InvalidRecordError('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:
raise InvalidRecordError('Unknown record id: {}'.format(record_id)) raise InvalidRecordError(f'Unknown record id: {record_id}')
if record_id == 0: if record_id == 0:
# Pad ''' Pad '''
pass pass
elif record_id == 1: elif record_id == 1:
''' Start '''
record = records.Start.read(stream, record_id) record = records.Start.read(stream, record_id)
record.merge_with_modals(modals) record.merge_with_modals(modals)
self.unit = record.unit self.unit = record.unit
self.version = record.version self.version = record.version
file_state.end_has_offset_table = record.offset_table is None file_state.end_has_offset_table = record.offset_table is None
file_state.property_target = self.properties
# TODO Offset table strict check # TODO Offset table strict check
elif record_id == 2: elif record_id == 2:
''' End '''
record = records.End.read(stream, record_id, file_state.end_has_offset_table) record = records.End.read(stream, record_id, file_state.end_has_offset_table)
record.merge_with_modals(modals) record.merge_with_modals(modals)
self.validation = record.validation self.validation = record.validation
@ -202,6 +229,7 @@ class OasisLayout:
raise InvalidRecordError('Stream continues past End record') raise InvalidRecordError('Stream continues past End record')
return True return True
elif record_id in (3, 4): elif record_id in (3, 4):
''' CellName '''
implicit = record_id == 3 implicit = record_id == 3
if file_state.cellname_implicit is None: if file_state.cellname_implicit is None:
file_state.cellname_implicit = implicit file_state.cellname_implicit = implicit
@ -213,8 +241,12 @@ class OasisLayout:
key = record.reference_number key = record.reference_number
if key is None: if key is None:
key = len(self.cellnames) key = len(self.cellnames)
self.cellnames[key] = record.nstring
cellname = CellName.from_record(record)
self.cellnames[key] = cellname
file_state.property_target = cellname.properties
elif record_id in (5, 6): elif record_id in (5, 6):
''' TextString '''
implicit = record_id == 5 implicit = record_id == 5
if file_state.textstring_implicit is None: if file_state.textstring_implicit is None:
file_state.textstring_implicit = implicit file_state.textstring_implicit = implicit
@ -228,6 +260,7 @@ class OasisLayout:
key = len(self.textstrings) key = len(self.textstrings)
self.textstrings[key] = record.astring self.textstrings[key] = record.astring
elif record_id in (7, 8): elif record_id in (7, 8):
''' PropName '''
implicit = record_id == 7 implicit = record_id == 7
if file_state.propname_implicit is None: if file_state.propname_implicit is None:
file_state.propname_implicit = implicit file_state.propname_implicit = implicit
@ -241,6 +274,7 @@ class OasisLayout:
key = len(self.propnames) key = len(self.propnames)
self.propnames[key] = record.nstring self.propnames[key] = record.nstring
elif record_id in (9, 10): elif record_id in (9, 10):
''' PropString '''
implicit = record_id == 9 implicit = record_id == 9
if file_state.propstring_implicit is None: if file_state.propstring_implicit is None:
file_state.propstring_implicit = implicit file_state.propstring_implicit = implicit
@ -254,17 +288,17 @@ class OasisLayout:
key = len(self.propstrings) key = len(self.propstrings)
self.propstrings[key] = record.astring self.propstrings[key] = record.astring
elif record_id in (11, 12): elif record_id in (11, 12):
''' LayerName '''
record = records.LayerName.read(stream, record_id) record = records.LayerName.read(stream, record_id)
record.merge_with_modals(modals) record.merge_with_modals(modals)
self.layers.append(record) self.layers.append(record)
elif record_id in (28, 29): elif record_id in (28, 29):
''' Property '''
record = records.Property.read(stream, record_id) record = records.Property.read(stream, record_id)
record.merge_with_modals(modals) record.merge_with_modals(modals)
if not file_state.within_cell: file_state.property_target.append(record)
self.properties.append(record)
else:
self.cells[-1].properties.append(record)
elif record_id in (30, 31): elif record_id in (30, 31):
''' XName '''
implicit = record_id == 30 implicit = record_id == 30
if file_state.xname_implicit is None: if file_state.xname_implicit is None:
file_state.xname_implicit = implicit file_state.xname_implicit = implicit
@ -277,53 +311,69 @@ class OasisLayout:
if key is None: if key is None:
key = len(self.xnames) key = len(self.xnames)
self.xnames[key] = XName.from_record(record) self.xnames[key] = XName.from_record(record)
# TODO: do anything with property target?
# #
# Cell and elements # Cell and elements
# #
elif record_id in (13, 14): elif record_id in (13, 14):
''' Cell '''
record = records.Cell.read(stream, record_id) record = records.Cell.read(stream, record_id)
record.merge_with_modals(modals) record.merge_with_modals(modals)
self.cells.append(Cell(record.name)) cell = Cell(record.name)
self.cells.append(cell)
file_state.property_target = cell.properties
elif record_id in (15, 16): elif record_id in (15, 16):
''' XYMode '''
record = records.XYMode.read(stream, record_id) record = records.XYMode.read(stream, record_id)
record.merge_with_modals(modals) record.merge_with_modals(modals)
elif record_id in (17, 18): elif record_id in (17, 18):
''' Placement '''
record = records.Placement.read(stream, record_id) record = records.Placement.read(stream, record_id)
record.merge_with_modals(modals) record.merge_with_modals(modals)
self.cells[-1].placements.append(record) self.cells[-1].placements.append(record)
file_state.property_target = record.properties
elif record_id in _GEOMETRY: elif record_id in _GEOMETRY:
''' Geometry '''
record = _GEOMETRY[record_id].read(stream, record_id) record = _GEOMETRY[record_id].read(stream, record_id)
record.merge_with_modals(modals) record.merge_with_modals(modals)
self.cells[-1].geometry.append(record) self.cells[-1].geometry.append(record)
file_state.property_target = record.properties
else: else:
raise InvalidRecordError('Unknown record id: {}'.format(record_id)) raise InvalidRecordError(f'Unknown record id: {record_id}')
return False return False
def write(self, stream: io.BufferedIOBase) -> int: def write(self, stream: IO[bytes]) -> int:
""" """
Write this object in OASIS fromat to a stream. Write this object in OASIS fromat to a stream.
:param stream: Stream to write to. Args:
:return: Number of bytes written. stream: Stream to write to.
:raises: InvalidDataError if contained records are invalid.
Returns:
Number of bytes written.
Raises:
InvalidDataError: if contained records are invalid.
""" """
modals = Modals() modals = Modals()
size = 0 size = 0
size += write_magic_bytes(stream) size += write_magic_bytes(stream)
size += records.Start(self.unit, self.version).dedup_write(stream, modals) 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) cellnames_offset = OffsetEntry(False, size)
size += sum(records.CellName(name, refnum).dedup_write(stream, modals) for refnum, cn in self.cellnames.items():
for refnum, name 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)
propnames_offset = OffsetEntry(False, size) propnames_offset = OffsetEntry(False, size)
size += sum(records.PropName(name, refnum).dedup_write(stream, modals) size += sum(records.PropName(name, refnum).dedup_write(stream, modals)
for refnum, name in self.propnames.items()) for refnum, name in self.propnames.items())
xnames_offset = OffsetEntry(False, size) xnames_offset = OffsetEntry(False, size)
size += sum(records.XName(x.attribute, x.string, refnum).dedup_write(stream, modals) size += sum(records.XName(x.attribute, x.bstring, refnum).dedup_write(stream, modals)
for refnum, x in self.xnames.items()) for refnum, x in self.xnames.items())
textstrings_offset = OffsetEntry(False, size) textstrings_offset = OffsetEntry(False, size)
@ -337,8 +387,6 @@ class OasisLayout:
layernames_offset = OffsetEntry(False, size) layernames_offset = OffsetEntry(False, size)
size += sum(r.dedup_write(stream, modals) for r in self.layers) 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) size += sum(c.dedup_write(stream, modals) for c in self.cells)
offset_table = OffsetTable( offset_table = OffsetTable(
@ -356,59 +404,109 @@ class OasisLayout:
class Cell: class Cell:
""" """
Representation of an OASIS 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 = None # type: NString or int name: NString | int
properties = None # type: List[records.Property] """name or "CellName reference" number"""
placements = None # type: List[records.Placement]
geometry = None # type: List[records.geometry_t]
def __init__(self, name: NString or int): properties: list[records.Property]
""" placements: list[records.Placement]
:param name: NString or int (CellName reference number) geometry: list[records.geometry_t]
"""
self.name = name
self.properties = []
self.placements = []
self.geometry = []
def dedup_write(self, stream: io.BufferedIOBase, modals: Modals) -> int: 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:
""" """
Write this cell to a stream, using the provided modal variables to Write this cell to a stream, using the provided modal variables to
deduplicate any repeated data. deduplicate any repeated data.
:param stream: Stream to write to. Args:
:param modals: Modal variables to use for deduplication. stream: Stream to write to.
:return: Number of bytes written. modals: Modal variables to use for deduplication.
:raises: InvalidDataError if contained records are invalid.
Returns:
Number of bytes written.
Raises:
InvalidDataError: if contained records are invalid.
""" """
size = records.Cell(self.name).dedup_write(stream, modals) size = records.Cell(self.name).dedup_write(stream, modals)
size += sum(p.dedup_write(stream, modals) for p in self.properties) size += sum(p.dedup_write(stream, modals) for p in self.properties)
size += sum(p.dedup_write(stream, modals) for p in self.placements) for placement in self.placements:
size += sum(g.dedup_write(stream, modals) for g in self.geometry) 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)
return size 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: class XName:
""" """
Representation of an 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. with the reference data stripped out.
""" """
attribute = None # type: int attribute: int
bstring = None # type: bytes bstring: bytes
def __init__(self, attribute: int, bstring: bytes): def __init__(self, attribute: int, bstring: bytes) -> None:
""" """
:param attribute: Attribute number. Args:
:param bstring: Binary data. attribute: Attribute number.
bstring: Binary data.
""" """
self.attribute = attribute self.attribute = attribute
self.bstring = bstring self.bstring = bstring
@ -416,16 +514,19 @@ class XName:
@staticmethod @staticmethod
def from_record(record: records.XName) -> 'XName': 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.
:param record: XName record to use. Args:
:return: XName object. record: XName record to use.
Returns:
a new `XName` object.
""" """
return XName(record.attribute, record.bstring) return XName(record.attribute, record.bstring)
# Mapping from record id to record class. # Mapping from record id to record class.
_GEOMETRY = { _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,

0
fatamorgana/py.typed Normal file
View File

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
"""
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.
"""

View File

@ -0,0 +1,97 @@
"""
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()

View File

@ -0,0 +1,196 @@
# 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],
])

View File

@ -0,0 +1,272 @@
# 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

View File

@ -0,0 +1,109 @@
# 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

View File

@ -0,0 +1,239 @@
# 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

View File

@ -0,0 +1,179 @@
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("<f", 12.5)) # dbu value: 12.5
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_4() -> 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("<d", 12.5)) # dbu value: 12.5
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_5() -> None:
buf = write_file_5(BytesIO())
buf.seek(0)
layout = OasisLayout.read(buf)
base_tests(layout)
assert layout.unit == 12.5

View File

@ -0,0 +1,335 @@
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)

View File

@ -0,0 +1,252 @@
# 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

View File

@ -0,0 +1,199 @@
# 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)

View File

@ -0,0 +1,890 @@
# 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

View File

@ -0,0 +1,451 @@
# 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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,273 @@
# 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

View File

@ -0,0 +1,735 @@
# 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)

View File

@ -0,0 +1,226 @@
# 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

View File

@ -0,0 +1,68 @@
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

41
fatamorgana/test/utils.py Normal file
View File

@ -0,0 +1,41 @@
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()

90
pyproject.toml Normal file
View File

@ -0,0 +1,90 @@
[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 :: 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",
"Topic :: Scientific/Engineering",
"Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)",
]
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
]

View File

@ -1,61 +0,0 @@
#!/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'],
},
)