forked from jan/fatamorgana
Compare commits
40 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
10 changed files with 1775 additions and 1243 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,6 +1,7 @@
|
|||
*.pyc
|
||||
__pycache__
|
||||
*.idea
|
||||
.mypy_cache/
|
||||
|
||||
build
|
||||
dist
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
include README.md
|
||||
include LICENSE.md
|
||||
include fatamorgana/VERSION
|
||||
|
|
|
|||
|
|
@ -26,12 +26,12 @@
|
|||
|
||||
Install with pip from PyPi (preferred):
|
||||
```bash
|
||||
pip install fatamorgana
|
||||
pip3 install fatamorgana
|
||||
```
|
||||
|
||||
Install directly from git repository:
|
||||
```bash
|
||||
pip install git+https://mpxd.net/code/jan/fatamorgana.git@release
|
||||
pip3 install git+https://mpxd.net/code/jan/fatamorgana.git@release
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
|
|
|||
1
fatamorgana/VERSION
Normal file
1
fatamorgana/VERSION
Normal file
|
|
@ -0,0 +1 @@
|
|||
0.8
|
||||
|
|
@ -16,13 +16,26 @@
|
|||
|
||||
Dependencies:
|
||||
- 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 .basic import NString, AString, Validation, OffsetTable, OffsetEntry, \
|
||||
EOFError, SignedError, InvalidDataError, InvalidRecordError
|
||||
EOFError, SignedError, InvalidDataError, InvalidRecordError, \
|
||||
UnfilledModalError, \
|
||||
ReuseRepetition, GridRepetition, ArbitraryRepetition
|
||||
|
||||
|
||||
__author__ = 'Jan Petykiewicz'
|
||||
|
||||
version = '0.4'
|
||||
with open(pathlib.Path(__file__).parent / 'VERSION', 'r') as f:
|
||||
__version__ = f.read().strip()
|
||||
version = __version__
|
||||
|
||||
|
|
|
|||
1147
fatamorgana/basic.py
1147
fatamorgana/basic.py
File diff suppressed because it is too large
Load diff
|
|
@ -3,11 +3,12 @@ This module contains data structures and functions for reading from and
|
|||
writing to whole OASIS layout files, and provides a few additional
|
||||
abstractions for the data contained inside them.
|
||||
"""
|
||||
from typing import List, Dict, Union, Optional, Type
|
||||
import io
|
||||
import logging
|
||||
|
||||
from . import records
|
||||
from .records import Modals
|
||||
from .records import Modals, Record
|
||||
from .basic import OffsetEntry, OffsetTable, NString, AString, real_t, Validation, \
|
||||
read_magic_bytes, write_magic_bytes, read_uint, EOFError, \
|
||||
InvalidDataError, InvalidRecordError
|
||||
|
|
@ -23,17 +24,16 @@ class FileModals:
|
|||
"""
|
||||
File-scoped modal variables
|
||||
"""
|
||||
cellname_implicit = None # type: bool or None
|
||||
propname_implicit = None # type: bool or None
|
||||
xname_implicit = None # type: bool or None
|
||||
textstring_implicit = None # type: bool or None
|
||||
propstring_implicit = None # type: bool or None
|
||||
cellname_implicit = None # type: bool or None
|
||||
cellname_implicit: Optional[bool] = None
|
||||
propname_implicit: Optional[bool] = None
|
||||
xname_implicit: Optional[bool] = None
|
||||
textstring_implicit: Optional[bool] = None
|
||||
propstring_implicit: Optional[bool] = None
|
||||
|
||||
within_cell = False # type: bool
|
||||
within_cblock = False # type: bool
|
||||
end_has_offset_table = None # type: bool
|
||||
started = False # type: bool
|
||||
within_cell: bool = False
|
||||
within_cblock: bool = False
|
||||
end_has_offset_table: bool
|
||||
started: bool = False
|
||||
|
||||
|
||||
class OasisLayout:
|
||||
|
|
@ -43,49 +43,51 @@ class OasisLayout:
|
|||
Names and strings are stored in dicts, indexed by reference number.
|
||||
Layer names and properties are stored directly using their associated
|
||||
record objects.
|
||||
Cells are stored using Cell objects (different from 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
|
||||
Attributes:
|
||||
(File properties)
|
||||
version (AString): Version string ('1.0')
|
||||
unit (real_t): grid steps per micron
|
||||
validation (Validation): checksum data
|
||||
|
||||
Names:
|
||||
.cellnames Dict[int, NString]
|
||||
.propnames Dict[int, NString]
|
||||
.xnames Dict[int, XName]
|
||||
(Names)
|
||||
cellnames (Dict[int, NString]): Cell names
|
||||
propnames (Dict[int, NString]): Property names
|
||||
xnames (Dict[int, XName]): Custom names
|
||||
|
||||
Strings:
|
||||
.textstrings Dict[int, AString]
|
||||
.propstrings Dict[int, AString]
|
||||
(Strings)
|
||||
textstrings (Dict[int, AString]): Text strings
|
||||
propstrings (Dict[int, AString]): Property strings
|
||||
|
||||
Data:
|
||||
.layers List[records.LayerName]
|
||||
.properties List[records.Property]
|
||||
.cells List[Cell]
|
||||
(Data)
|
||||
layers (List[records.LayerName]): Layer definitions
|
||||
properties (List[records.Property]): Property values
|
||||
cells (List[Cell]): Layout cells
|
||||
"""
|
||||
version = None # type: AString
|
||||
unit = None # type: real_t
|
||||
validation = None # type: Validation
|
||||
version: AString
|
||||
unit: real_t
|
||||
validation: Validation
|
||||
|
||||
properties = None # type: List[records.Property]
|
||||
cells = None # type: List[Cell]
|
||||
properties: List[records.Property]
|
||||
cells: List['Cell']
|
||||
|
||||
cellnames = None # type: Dict[int, NString]
|
||||
propnames = None # type: Dict[int, NString]
|
||||
xnames = None # type: Dict[int, XName]
|
||||
cellnames: Dict[int, NString]
|
||||
propnames: Dict[int, NString]
|
||||
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):
|
||||
"""
|
||||
:param unit: Real number (i.e. int, float, or Fraction), grid steps per micron.
|
||||
:param validation: Validation object containing checksum data.
|
||||
Default creates a Validation object of the "no checksum" type.
|
||||
Args:
|
||||
unit: Real number (i.e. int, float, or `Fraction`), grid steps per micron.
|
||||
validation: `Validation` object containing checksum data.
|
||||
Default creates a `Validation` object of the "no checksum" type.
|
||||
"""
|
||||
if validation is None:
|
||||
validation = Validation(0)
|
||||
|
|
@ -105,14 +107,17 @@ class OasisLayout:
|
|||
@staticmethod
|
||||
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.
|
||||
:return: New OasisLayout object.
|
||||
Args:
|
||||
stream: Stream to read from.
|
||||
|
||||
Returns:
|
||||
New `OasisLayout` object.
|
||||
"""
|
||||
file_state = FileModals()
|
||||
modals = Modals()
|
||||
layout = OasisLayout(unit=None)
|
||||
layout = OasisLayout(unit=-1) # dummy unit
|
||||
|
||||
read_magic_bytes(stream)
|
||||
|
||||
|
|
@ -127,15 +132,20 @@ class OasisLayout:
|
|||
) -> bool:
|
||||
"""
|
||||
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.
|
||||
:param modals: Modal variable data, used to fill unfilled record
|
||||
fields and updated using filled record fields.
|
||||
:param file_state: File status data.
|
||||
:return: True if EOF was reached without error, False otherwise.
|
||||
:raises: InvalidRecordError from unexpected records;
|
||||
InvalidDataError from within record parsers.
|
||||
Args:
|
||||
stream: Stream to read from.
|
||||
modals: Modal variable data, used to fill unfilled record
|
||||
fields and updated using filled record fields.
|
||||
file_state: File status data.
|
||||
|
||||
Returns:
|
||||
`True` if EOF was reached without error, `False` otherwise.
|
||||
|
||||
Raises:
|
||||
InvalidRecordError: from unexpected records
|
||||
InvalidDataError: from within record parsers
|
||||
"""
|
||||
try:
|
||||
record_id = read_uint(stream)
|
||||
|
|
@ -147,6 +157,8 @@ class OasisLayout:
|
|||
|
||||
logger.info('read_record of type {} at position 0x{:x}'.format(record_id, stream.tell()))
|
||||
|
||||
record: Record
|
||||
|
||||
# CBlock
|
||||
if record_id == 34:
|
||||
if file_state.within_cblock:
|
||||
|
|
@ -304,9 +316,14 @@ class OasisLayout:
|
|||
"""
|
||||
Write this object in OASIS fromat to a stream.
|
||||
|
||||
:param stream: Stream to write to.
|
||||
:return: Number of bytes written.
|
||||
:raises: InvalidDataError if contained records are invalid.
|
||||
Args:
|
||||
stream: Stream to write to.
|
||||
|
||||
Returns:
|
||||
Number of bytes written.
|
||||
|
||||
Raises:
|
||||
InvalidDataError: if contained records are invalid.
|
||||
"""
|
||||
modals = Modals()
|
||||
|
||||
|
|
@ -323,7 +340,7 @@ class OasisLayout:
|
|||
for refnum, name in self.propnames.items())
|
||||
|
||||
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())
|
||||
|
||||
textstrings_offset = OffsetEntry(False, size)
|
||||
|
|
@ -357,23 +374,24 @@ class Cell:
|
|||
"""
|
||||
Representation of an OASIS cell.
|
||||
|
||||
Properties:
|
||||
.name NString or int (CellName reference number)
|
||||
Attributes:
|
||||
name (Union[NString, int]): name or "CellName reference" number
|
||||
|
||||
.properties List of records.Property
|
||||
.placements List of records.Placement
|
||||
.geometry List of geometry record objectes
|
||||
properties (List[records.Property]): Properties of this cell
|
||||
placements (List[records.Placement]): Placement record objects
|
||||
geometry: (List[records.geometry_t]): Geometry record objectes
|
||||
"""
|
||||
name = None # type: NString or int
|
||||
properties = None # type: List[records.Property]
|
||||
placements = None # type: List[records.Placement]
|
||||
geometry = None # type: List[records.geometry_t]
|
||||
name: Union[NString, int]
|
||||
properties: List[records.Property]
|
||||
placements: List[records.Placement]
|
||||
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)
|
||||
Args:
|
||||
name: `NString` or "CellName reference" number
|
||||
"""
|
||||
self.name = name
|
||||
self.name = name if isinstance(name, (NString, int)) else NString(name)
|
||||
self.properties = []
|
||||
self.placements = []
|
||||
self.geometry = []
|
||||
|
|
@ -383,10 +401,15 @@ class Cell:
|
|||
Write this cell to a stream, using the provided modal variables to
|
||||
deduplicate any repeated data.
|
||||
|
||||
:param stream: Stream to write to.
|
||||
:param modals: Modal variables to use for deduplication.
|
||||
:return: Number of bytes written.
|
||||
:raises: InvalidDataError if contained records are invalid.
|
||||
Args:
|
||||
stream: Stream to write to.
|
||||
modals: Modal variables to use for deduplication.
|
||||
|
||||
Returns:
|
||||
Number of bytes written.
|
||||
|
||||
Raises:
|
||||
InvalidDataError: if contained records are invalid.
|
||||
"""
|
||||
size = records.Cell(self.name).dedup_write(stream, modals)
|
||||
size += sum(p.dedup_write(stream, modals) for p in self.properties)
|
||||
|
|
@ -399,16 +422,17 @@ class XName:
|
|||
"""
|
||||
Representation of an XName.
|
||||
|
||||
This class is effectively a simplified form of a records.XName,
|
||||
This class is effectively a simplified form of a `records.XName`,
|
||||
with the reference data stripped out.
|
||||
"""
|
||||
attribute = None # type: int
|
||||
bstring = None # type: bytes
|
||||
attribute: int
|
||||
bstring: bytes
|
||||
|
||||
def __init__(self, attribute: int, bstring: bytes):
|
||||
"""
|
||||
:param attribute: Attribute number.
|
||||
:param bstring: Binary data.
|
||||
Args:
|
||||
attribute: Attribute number.
|
||||
bstring: Binary data.
|
||||
"""
|
||||
self.attribute = attribute
|
||||
self.bstring = bstring
|
||||
|
|
@ -416,16 +440,19 @@ class XName:
|
|||
@staticmethod
|
||||
def from_record(record: records.XName) -> 'XName':
|
||||
"""
|
||||
Create an XName object from a records.XName record.
|
||||
Create an `XName` object from a `records.XName` record.
|
||||
|
||||
:param record: XName record to use.
|
||||
:return: XName object.
|
||||
Args:
|
||||
record: XName record to use.
|
||||
|
||||
Returns:
|
||||
`XName` object.
|
||||
"""
|
||||
return XName(record.attribute, record.bstring)
|
||||
|
||||
|
||||
# Mapping from record id to record class.
|
||||
_GEOMETRY = {
|
||||
_GEOMETRY: Dict[int, Type] = {
|
||||
19: records.Text,
|
||||
20: records.Rectangle,
|
||||
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
13
setup.py
13
setup.py
|
|
@ -1,13 +1,15 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
import fatamorgana
|
||||
|
||||
with open('README.md', 'r') as f:
|
||||
long_description = f.read()
|
||||
|
||||
with open('fatamorgana/VERSION', 'r') as f:
|
||||
version = f.read().strip()
|
||||
|
||||
setup(name='fatamorgana',
|
||||
version=fatamorgana.version,
|
||||
version=version,
|
||||
description='OASIS layout format parser and writer',
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/markdown',
|
||||
|
|
@ -35,7 +37,6 @@ setup(name='fatamorgana',
|
|||
'gds',
|
||||
],
|
||||
classifiers=[
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Development Status :: 3 - Alpha',
|
||||
'Environment :: Other Environment',
|
||||
|
|
@ -48,9 +49,13 @@ setup(name='fatamorgana',
|
|||
'Operating System :: Microsoft :: Windows',
|
||||
'Topic :: Scientific/Engineering',
|
||||
'Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
],
|
||||
packages=find_packages(),
|
||||
package_data={
|
||||
'fatamorgana': ['VERSION',
|
||||
'py.typed',
|
||||
],
|
||||
},
|
||||
install_requires=[
|
||||
'typing',
|
||||
],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue