forked from jan/fatamorgana
Compare commits
98 Commits
Author | SHA1 | Date | |
---|---|---|---|
968392a4a3 | |||
cc08efcf17 | |||
19324ee147 | |||
f4866fb2bb | |||
37bac2af0d | |||
d3ba2ca2af | |||
3ee1c03f66 | |||
1252edb7b5 | |||
8e451c64db | |||
691ce03150 | |||
dc9ed8e794 | |||
891007054f | |||
5011750637 | |||
f87dc7d771 | |||
31e52e20d6 | |||
5ea5e8d8f9 | |||
d05561af41 | |||
ab8b71b149 | |||
c9e894879f | |||
d83ef1ce2d | |||
bd288d1363 | |||
931bc599d7 | |||
a4c1e52ff8 | |||
d61bbc530f | |||
01b3f9ca3a | |||
b2928c8b1c | |||
f9d4cfec33 | |||
7efc5a39d4 | |||
9c798b9906 | |||
5a42081f6a | |||
c0e7d11ea2 | |||
90ea0c2195 | |||
f3be3deadb | |||
9c5b902a33 | |||
fd9f16d705 | |||
48d52f56c7 | |||
d73e13d13b | |||
a64f2726d0 | |||
25fe2067cd | |||
f8a65968cb | |||
748bb497d1 | |||
08ab2b41d5 | |||
ba6b04d82d | |||
f9b8bf823b | |||
b996f5f27e | |||
60f879a1ad | |||
4a878aa7bf | |||
c1b79485a7 | |||
3ca999fa2e | |||
3627b63658 | |||
167b16e1c9 | |||
2c2013a0fc | |||
a80ac6199a | |||
97f2bb1238 | |||
bc15a66ecc | |||
5ac774c386 | |||
4b7b6b82c1 | |||
94555c1b6e | |||
86c1e4cd3b | |||
fdf5e9f598 | |||
fab80c8517 | |||
492d6416db | |||
aaef122178 | |||
55638fcde5 | |||
99283aaaf0 | |||
705926d443 | |||
e4a62a0f32 | |||
715fe7ea24 | |||
d25f3f1598 | |||
e909aa958d | |||
411012079d | |||
f15499030d | |||
73310fe993 | |||
3a2d889360 | |||
3b01117329 | |||
4c5f649eec | |||
25b2cecec9 | |||
c9adca61b6 | |||
1a259a1c19 | |||
3af40dd1bc | |||
8fb2f2b594 | |||
cfb3e90d71 | |||
7f0c46525e | |||
e9cf010f54 | |||
06de10062d | |||
62fca39a69 | |||
6f2200c5ed | |||
e046af8ce8 | |||
533c85b249 | |||
bb9ebfc8f9 | |||
f4eeb50a6f | |||
58b4f4a40f | |||
b5a7c9a7ad | |||
deb0fe3bef | |||
e0c2947865 | |||
e514ade2b1 | |||
203a520770 | |||
ab175641fe |
30
.flake8
Normal file
30
.flake8
Normal 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
7
.gitignore
vendored
@ -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
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
include README.md
|
|
||||||
include LICENSE.md
|
|
@ -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
1
fatamorgana/LICENSE.md
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../LICENSE.md
|
1
fatamorgana/README.md
Symbolic link
1
fatamorgana/README.md
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../README.md
|
@ -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__
|
||||||
|
1519
fatamorgana/basic.py
1519
fatamorgana/basic.py
File diff suppressed because it is too large
Load Diff
@ -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,
|
||||||
|
stream: IO[bytes],
|
||||||
modals: Modals,
|
modals: Modals,
|
||||||
file_state: FileModals
|
file_state: FileModals
|
||||||
) -> bool:
|
) -> 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.
|
||||||
|
modals: Modal variable data, used to fill unfilled record
|
||||||
fields and updated using filled record fields.
|
fields and updated using filled record fields.
|
||||||
:param file_state: File status data.
|
file_state: File status data.
|
||||||
:return: True if EOF was reached without error, False otherwise.
|
|
||||||
:raises: InvalidRecordError from unexpected records;
|
Returns:
|
||||||
InvalidDataError from within record parsers.
|
`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,11 +188,10 @@ 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
0
fatamorgana/py.typed
Normal file
File diff suppressed because it is too large
Load Diff
7
fatamorgana/test/__init__.py
Normal file
7
fatamorgana/test/__init__.py
Normal 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.
|
||||||
|
"""
|
97
fatamorgana/test/build_testfiles.py
Normal file
97
fatamorgana/test/build_testfiles.py
Normal 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()
|
196
fatamorgana/test/test_files_cblocks.py
Normal file
196
fatamorgana/test/test_files_cblocks.py
Normal 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],
|
||||||
|
])
|
272
fatamorgana/test/test_files_cells.py
Normal file
272
fatamorgana/test/test_files_cells.py
Normal 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
|
109
fatamorgana/test/test_files_circles.py
Normal file
109
fatamorgana/test/test_files_circles.py
Normal 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
|
239
fatamorgana/test/test_files_ctrapezoids.py
Normal file
239
fatamorgana/test/test_files_ctrapezoids.py
Normal 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
|
||||||
|
|
179
fatamorgana/test/test_files_empty.py
Normal file
179
fatamorgana/test/test_files_empty.py
Normal 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
|
335
fatamorgana/test/test_files_layernames.py
Normal file
335
fatamorgana/test/test_files_layernames.py
Normal 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)
|
252
fatamorgana/test/test_files_modals.py
Normal file
252
fatamorgana/test/test_files_modals.py
Normal 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
|
199
fatamorgana/test/test_files_paths.py
Normal file
199
fatamorgana/test/test_files_paths.py
Normal 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)
|
890
fatamorgana/test/test_files_placements.py
Normal file
890
fatamorgana/test/test_files_placements.py
Normal 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
|
451
fatamorgana/test/test_files_polygons.py
Normal file
451
fatamorgana/test/test_files_polygons.py
Normal 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
|
||||||
|
|
1078
fatamorgana/test/test_files_properties.py
Normal file
1078
fatamorgana/test/test_files_properties.py
Normal file
File diff suppressed because it is too large
Load Diff
273
fatamorgana/test/test_files_rectangles.py
Normal file
273
fatamorgana/test/test_files_rectangles.py
Normal 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
|
||||||
|
|
735
fatamorgana/test/test_files_texts.py
Normal file
735
fatamorgana/test/test_files_texts.py
Normal 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)
|
226
fatamorgana/test/test_files_trapezoids.py
Normal file
226
fatamorgana/test/test_files_trapezoids.py
Normal 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
|
68
fatamorgana/test/test_int.py
Normal file
68
fatamorgana/test/test_int.py
Normal 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
41
fatamorgana/test/utils.py
Normal 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
90
pyproject.toml
Normal 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
|
||||||
|
]
|
||||||
|
|
61
setup.py
61
setup.py
@ -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'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user