forked from jan/fatamorgana
Compare commits
55 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
11 changed files with 2006 additions and 1327 deletions
29
.flake8
Normal file
29
.flake8
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
[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,
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,7 +1,9 @@
|
||||||
*.pyc
|
*.pyc
|
||||||
__pycache__
|
__pycache__
|
||||||
*.idea
|
*.idea
|
||||||
|
.mypy_cache/
|
||||||
|
|
||||||
build
|
build
|
||||||
dist
|
dist
|
||||||
fatamorgana.egg-info
|
fatamorgana.egg-info
|
||||||
|
docs
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
include README.md
|
include README.md
|
||||||
include LICENSE.md
|
include LICENSE.md
|
||||||
|
include fatamorgana/VERSION
|
||||||
|
|
|
||||||
10
README.md
10
README.md
|
|
@ -1,4 +1,4 @@
|
||||||
# fatamorgana
|
# fatamorgana
|
||||||
|
|
||||||
**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.
|
||||||
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
**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
|
||||||
the tasks I usually use it for, but I can't guarantee I've even
|
the tasks I usually use it for, but I can't guarantee I've even
|
||||||
tried the features you happen to use! Use at your own risk!
|
tried the features you happen to use! Use at your own risk!
|
||||||
* Interfaces and datastructures are subject to change!
|
* Interfaces and datastructures are subject to change!
|
||||||
* That said the following work for me:
|
* That said the following work for me:
|
||||||
|
|
@ -26,12 +26,12 @@
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -53,7 +53,7 @@ Read an OASIS file and write it back out:
|
||||||
|
|
||||||
with open('test.oas', 'rb') as f:
|
with open('test.oas', 'rb') as f:
|
||||||
layout = fatamorgana.OasisLayout.read(f)
|
layout = fatamorgana.OasisLayout.read(f)
|
||||||
|
|
||||||
with open('test_write.oas', 'wb') as f:
|
with open('test_write.oas', 'wb') as f:
|
||||||
layout.write(f)
|
layout.write(f)
|
||||||
```
|
```
|
||||||
|
|
|
||||||
4
fatamorgana/VERSION.py
Normal file
4
fatamorgana/VERSION.py
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
""" VERSION defintion. THIS FILE IS MANUALLY PARSED BY setup.py and REQUIRES A SPECIFIC FORMAT """
|
||||||
|
__version__ = '''
|
||||||
|
0.11
|
||||||
|
'''
|
||||||
|
|
@ -16,13 +16,26 @@
|
||||||
|
|
||||||
Dependencies:
|
Dependencies:
|
||||||
- Python 3.5 or later
|
- Python 3.5 or later
|
||||||
- numpy (optional, no additional functionality)
|
- numpy (optional, faster but no additional functionality)
|
||||||
|
|
||||||
|
To get started, try:
|
||||||
|
```python3
|
||||||
|
import fatamorgana
|
||||||
|
help(fatamorgana.OasisLayout)
|
||||||
|
```
|
||||||
"""
|
"""
|
||||||
|
import pathlib
|
||||||
|
|
||||||
from .main import OasisLayout, Cell, XName
|
from .main import OasisLayout, Cell, XName
|
||||||
from .basic import NString, AString, Validation, OffsetTable, OffsetEntry, \
|
from .basic import (
|
||||||
EOFError, SignedError, InvalidDataError, InvalidRecordError
|
NString, AString, Validation, OffsetTable, OffsetEntry,
|
||||||
|
EOFError, SignedError, InvalidDataError, InvalidRecordError,
|
||||||
|
UnfilledModalError,
|
||||||
|
ReuseRepetition, GridRepetition, ArbitraryRepetition
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
__author__ = 'Jan Petykiewicz'
|
__author__ = 'Jan Petykiewicz'
|
||||||
|
|
||||||
version = '0.4'
|
from .VERSION import __version__
|
||||||
|
version = __version__
|
||||||
|
|
|
||||||
1179
fatamorgana/basic.py
1179
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 List, Dict, Union, Optional, Type
|
||||||
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: Optional[bool] = None
|
||||||
propname_implicit = None # type: bool or None
|
propname_implicit: Optional[bool] = None
|
||||||
xname_implicit = None # type: bool or None
|
xname_implicit: Optional[bool] = None
|
||||||
textstring_implicit = None # type: bool or None
|
textstring_implicit: Optional[bool] = None
|
||||||
propstring_implicit = None # type: bool or None
|
propstring_implicit: Optional[bool] = 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]):
|
||||||
|
self.property_target = property_target
|
||||||
|
|
||||||
|
|
||||||
class OasisLayout:
|
class OasisLayout:
|
||||||
|
|
@ -43,49 +51,50 @@ 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:
|
Attributes:
|
||||||
File properties:
|
(File properties)
|
||||||
.version AString: Version string ('1.0')
|
version (AString): Version string ('1.0')
|
||||||
.unit real number: grid steps per micron
|
unit (real_t): grid steps per micron
|
||||||
.validation Validation: checksum data
|
validation (Validation): checksum data
|
||||||
|
|
||||||
Names:
|
(Names)
|
||||||
.cellnames Dict[int, NString]
|
cellnames (Dict[int, CellName]): Cell names
|
||||||
.propnames Dict[int, NString]
|
propnames (Dict[int, NString]): Property names
|
||||||
.xnames Dict[int, XName]
|
xnames (Dict[int, XName]): Custom names
|
||||||
|
|
||||||
Strings:
|
(Strings)
|
||||||
.textstrings Dict[int, AString]
|
textstrings (Dict[int, AString]): Text strings
|
||||||
.propstrings Dict[int, AString]
|
propstrings (Dict[int, AString]): Property strings
|
||||||
|
|
||||||
Data:
|
(Data)
|
||||||
.layers List[records.LayerName]
|
layers (List[records.LayerName]): Layer definitions
|
||||||
.properties List[records.Property]
|
properties (List[records.Property]): Property values
|
||||||
.cells List[Cell]
|
cells (List[Cell]): Layout cells
|
||||||
"""
|
"""
|
||||||
version = None # type: AString
|
version: AString
|
||||||
unit = None # type: real_t
|
unit: real_t
|
||||||
validation = None # type: Validation
|
validation: Validation
|
||||||
|
|
||||||
properties = None # type: List[records.Property]
|
properties: List[records.Property]
|
||||||
cells = None # type: List[Cell]
|
cells: List['Cell']
|
||||||
|
|
||||||
cellnames = None # type: Dict[int, NString]
|
cellnames: Dict[int, 'CellName']
|
||||||
propnames = None # type: Dict[int, NString]
|
propnames: Dict[int, NString]
|
||||||
xnames = None # type: Dict[int, XName]
|
xnames: Dict[int, 'XName']
|
||||||
|
|
||||||
textstrings = None # type: Dict[int, AString]
|
|
||||||
propstrings = None # type: Dict[int, AString]
|
|
||||||
layers = None # type: List[records.LayerName]
|
|
||||||
|
|
||||||
|
textstrings: Dict[int, AString]
|
||||||
|
propstrings: Dict[int, AString]
|
||||||
|
layers: List[records.LayerName]
|
||||||
|
|
||||||
def __init__(self, unit: real_t, validation: Validation = None):
|
def __init__(self, unit: real_t, validation: Validation = 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)
|
||||||
|
|
@ -105,14 +114,17 @@ class OasisLayout:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def read(stream: io.BufferedIOBase) -> 'OasisLayout':
|
def read(stream: io.BufferedIOBase) -> 'OasisLayout':
|
||||||
"""
|
"""
|
||||||
Read an entire .oas file into an OasisLayout object.
|
Read an entire .oas file into an `OasisLayout` object.
|
||||||
|
|
||||||
: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)
|
||||||
|
|
||||||
|
|
@ -127,15 +139,20 @@ class OasisLayout:
|
||||||
) -> 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.
|
||||||
fields and updated using filled record fields.
|
modals: Modal variable data, used to fill unfilled record
|
||||||
:param file_state: File status data.
|
fields and updated using filled record fields.
|
||||||
:return: True if EOF was reached without error, False otherwise.
|
file_state: File status data.
|
||||||
:raises: InvalidRecordError from unexpected records;
|
|
||||||
InvalidDataError from within record parsers.
|
Returns:
|
||||||
|
`True` if EOF was reached without error, `False` otherwise.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
InvalidRecordError: from unexpected records
|
||||||
|
InvalidDataError: from within record parsers
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
record_id = read_uint(stream)
|
record_id = read_uint(stream)
|
||||||
|
|
@ -147,6 +164,8 @@ class OasisLayout:
|
||||||
|
|
||||||
logger.info('read_record of type {} at position 0x{:x}'.format(record_id, stream.tell()))
|
logger.info('read_record of type {} at position 0x{:x}'.format(record_id, stream.tell()))
|
||||||
|
|
||||||
|
record: Record
|
||||||
|
|
||||||
# CBlock
|
# CBlock
|
||||||
if record_id == 34:
|
if record_id == 34:
|
||||||
if file_state.within_cblock:
|
if file_state.within_cblock:
|
||||||
|
|
@ -185,16 +204,19 @@ class OasisLayout:
|
||||||
raise InvalidRecordError('Unknown record id: {}'.format(record_id))
|
raise InvalidRecordError('Unknown record id: {}'.format(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 +224,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 +236,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 +255,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 +269,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 +283,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,25 +306,34 @@ 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('Unknown record id: {}'.format(record_id))
|
||||||
return False
|
return False
|
||||||
|
|
@ -304,26 +342,33 @@ class OasisLayout:
|
||||||
"""
|
"""
|
||||||
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 +382,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(
|
||||||
|
|
@ -357,58 +400,110 @@ class Cell:
|
||||||
"""
|
"""
|
||||||
Representation of an OASIS cell.
|
Representation of an OASIS cell.
|
||||||
|
|
||||||
Properties:
|
Attributes:
|
||||||
.name NString or int (CellName reference number)
|
name (Union[NString, int]): name or "CellName reference" number
|
||||||
|
|
||||||
.properties List of records.Property
|
properties (List[records.Property]): Properties of this cell
|
||||||
.placements List of records.Placement
|
placements (List[records.Placement]): Placement record objects
|
||||||
.geometry List of geometry record objectes
|
geometry: (List[records.geometry_t]): Geometry record objectes
|
||||||
"""
|
"""
|
||||||
name = None # type: NString or int
|
name: Union[NString, int]
|
||||||
properties = None # type: List[records.Property]
|
properties: List[records.Property]
|
||||||
placements = None # type: List[records.Placement]
|
placements: List[records.Placement]
|
||||||
geometry = None # type: List[records.geometry_t]
|
geometry: List[records.geometry_t]
|
||||||
|
|
||||||
def __init__(self, name: NString or int):
|
def __init__(self,
|
||||||
"""
|
name: Union[NString, str, int],
|
||||||
:param name: NString or int (CellName reference number)
|
*,
|
||||||
"""
|
properties: Optional[List[records.Property]] = None,
|
||||||
self.name = name
|
placements: Optional[List[records.Placement]] = None,
|
||||||
self.properties = []
|
geometry: Optional[List[records.geometry_t]] = None,
|
||||||
self.placements = []
|
):
|
||||||
self.geometry = []
|
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.BufferedIOBase, modals: Modals) -> int:
|
def dedup_write(self, stream: io.BufferedIOBase, modals: Modals) -> int:
|
||||||
"""
|
"""
|
||||||
Write this cell to a stream, using the provided modal variables to
|
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: Union[NString, str],
|
||||||
|
properties: Optional[List[records.Property]] = 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):
|
||||||
"""
|
"""
|
||||||
: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 +511,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
52
setup.py
52
setup.py
|
|
@ -1,19 +1,44 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
import fatamorgana
|
|
||||||
|
|
||||||
with open('README.md', 'r') as f:
|
with open('README.md', 'r') as f:
|
||||||
long_description = f.read()
|
long_description = f.read()
|
||||||
|
|
||||||
|
with open('fatamorgana/VERSION.py', 'rt') as f:
|
||||||
|
version = f.readlines()[2].strip()
|
||||||
|
|
||||||
setup(name='fatamorgana',
|
setup(name='fatamorgana',
|
||||||
version=fatamorgana.version,
|
version=version,
|
||||||
description='OASIS layout format parser and writer',
|
description='OASIS layout format parser and writer',
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
long_description_content_type='text/markdown',
|
long_description_content_type='text/markdown',
|
||||||
author='Jan Petykiewicz',
|
author='Jan Petykiewicz',
|
||||||
author_email='anewusername@gmail.com',
|
author_email='anewusername@gmail.com',
|
||||||
url='https://mpxd.net/code/jan/fatamorgana',
|
url='https://mpxd.net/code/jan/fatamorgana',
|
||||||
|
packages=find_packages(),
|
||||||
|
package_data={
|
||||||
|
'fatamorgana': ['py.typed'],
|
||||||
|
},
|
||||||
|
install_requires=[
|
||||||
|
'typing',
|
||||||
|
],
|
||||||
|
extras_require={
|
||||||
|
'numpy': ['numpy'],
|
||||||
|
},
|
||||||
|
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)',
|
||||||
|
],
|
||||||
keywords=[
|
keywords=[
|
||||||
'OASIS',
|
'OASIS',
|
||||||
'layout',
|
'layout',
|
||||||
|
|
@ -34,28 +59,5 @@ setup(name='fatamorgana',
|
||||||
'polygon',
|
'polygon',
|
||||||
'gds',
|
'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…
Add table
Add a link
Reference in a new issue