Compare commits

..

No commits in common. "v0.7" and "master" have entirely different histories.

10 changed files with 1239 additions and 1771 deletions

1
.gitignore vendored
View file

@ -1,7 +1,6 @@
*.pyc *.pyc
__pycache__ __pycache__
*.idea *.idea
.mypy_cache/
build build
dist dist

View file

@ -1,3 +1,2 @@
include README.md include README.md
include LICENSE.md include LICENSE.md
include fatamorgana/VERSION

View file

@ -26,12 +26,12 @@
Install with pip from PyPi (preferred): Install with pip from PyPi (preferred):
```bash ```bash
pip3 install fatamorgana pip install fatamorgana
``` ```
Install directly from git repository: Install directly from git repository:
```bash ```bash
pip3 install git+https://mpxd.net/code/jan/fatamorgana.git@release pip install git+https://mpxd.net/code/jan/fatamorgana.git@release
``` ```
## Documentation ## Documentation

View file

@ -1 +0,0 @@
0.7

View file

@ -16,26 +16,13 @@
Dependencies: Dependencies:
- Python 3.5 or later - Python 3.5 or later
- numpy (optional, faster but no additional functionality) - numpy (optional, 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 NString, AString, Validation, OffsetTable, OffsetEntry, \
EOFError, SignedError, InvalidDataError, InvalidRecordError, \ EOFError, SignedError, InvalidDataError, InvalidRecordError
UnfilledModalError, \
ReuseRepetition, GridRepetition, ArbitraryRepetition
__author__ = 'Jan Petykiewicz' __author__ = 'Jan Petykiewicz'
with open(pathlib.Path(__file__).parent / 'VERSION', 'r') as f: version = '0.4'
__version__ = f.read().strip()
version = __version__

File diff suppressed because it is too large Load diff

View file

@ -3,12 +3,11 @@ 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, Record from .records import Modals
from .basic import OffsetEntry, OffsetTable, NString, AString, real_t, Validation, \ from .basic import OffsetEntry, OffsetTable, NString, AString, real_t, Validation, \
read_magic_bytes, write_magic_bytes, read_uint, EOFError, \ read_magic_bytes, write_magic_bytes, read_uint, EOFError, \
InvalidDataError, InvalidRecordError InvalidDataError, InvalidRecordError
@ -24,16 +23,17 @@ class FileModals:
""" """
File-scoped modal variables File-scoped modal variables
""" """
cellname_implicit: Optional[bool] = None cellname_implicit = None # type: bool or None
propname_implicit: Optional[bool] = None propname_implicit = None # type: bool or None
xname_implicit: Optional[bool] = None xname_implicit = None # type: bool or None
textstring_implicit: Optional[bool] = None textstring_implicit = None # type: bool or None
propstring_implicit: Optional[bool] = None propstring_implicit = None # type: bool or None
cellname_implicit = None # type: bool or None
within_cell: bool = False within_cell = False # type: bool
within_cblock: bool = False within_cblock = False # type: bool
end_has_offset_table: bool end_has_offset_table = None # type: bool
started: bool = False started = False # type: bool
class OasisLayout: class OasisLayout:
@ -43,51 +43,49 @@ 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 `records.Cell` Cells are stored using Cell objects (different from Cell record objects).
record objects).
Attributes: Properties:
(File properties) File properties:
version (AString): Version string ('1.0') .version AString: Version string ('1.0')
unit (real_t): grid steps per micron .unit real number: grid steps per micron
validation (Validation): checksum data .validation Validation: checksum data
(Names) Names:
cellnames (Dict[int, NString]): Cell names .cellnames Dict[int, NString]
propnames (Dict[int, NString]): Property names .propnames Dict[int, NString]
xnames (Dict[int, XName]): Custom names .xnames Dict[int, XName]
(Strings) Strings:
textstrings (Dict[int, AString]): Text strings .textstrings Dict[int, AString]
propstrings (Dict[int, AString]): Property strings .propstrings Dict[int, AString]
(Data) Data:
layers (List[records.LayerName]): Layer definitions .layers List[records.LayerName]
properties (List[records.Property]): Property values .properties List[records.Property]
cells (List[Cell]): Layout cells .cells List[Cell]
""" """
version: AString version = None # type: AString
unit: real_t unit = None # type: real_t
validation: Validation validation = None # type: Validation
properties: List[records.Property] properties = None # type: List[records.Property]
cells: List['Cell'] cells = None # type: List[Cell]
cellnames: Dict[int, NString] cellnames = None # type: Dict[int, NString]
propnames: Dict[int, NString] propnames = None # type: Dict[int, NString]
xnames: Dict[int, 'XName'] xnames = None # type: Dict[int, XName]
textstrings: Dict[int, AString] textstrings = None # type: Dict[int, AString]
propstrings: Dict[int, AString] propstrings = None # type: Dict[int, AString]
layers: List[records.LayerName] layers = None # type: List[records.LayerName]
def __init__(self, unit: real_t, validation: Validation = None): def __init__(self, unit: real_t, validation: Validation = None):
""" """
Args: :param unit: Real number (i.e. int, float, or Fraction), grid steps per micron.
unit: Real number (i.e. int, float, or `Fraction`), grid steps per micron. :param validation: Validation object containing checksum data.
validation: `Validation` object containing checksum data. Default creates a Validation object of the "no checksum" type.
Default creates a `Validation` object of the "no checksum" type.
""" """
if validation is None: if validation is None:
validation = Validation(0) validation = Validation(0)
@ -107,17 +105,14 @@ 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.
Args: :param stream: Stream to read from.
stream: Stream to read from. :return: New OasisLayout object.
Returns:
New `OasisLayout` object.
""" """
file_state = FileModals() file_state = FileModals()
modals = Modals() modals = Modals()
layout = OasisLayout(unit=-1) # dummy unit layout = OasisLayout(unit=None)
read_magic_bytes(stream) read_magic_bytes(stream)
@ -132,20 +127,15 @@ 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.
Args: :param stream: Stream to read from.
stream: Stream to read from. :param modals: Modal variable data, used to fill unfilled record
modals: Modal variable data, used to fill unfilled record
fields and updated using filled record fields. fields and updated using filled record fields.
file_state: File status data. :param file_state: File status data.
:return: True if EOF was reached without error, False otherwise.
Returns: :raises: InvalidRecordError from unexpected records;
`True` if EOF was reached without error, `False` otherwise. InvalidDataError from within record parsers.
Raises:
InvalidRecordError: from unexpected records
InvalidDataError: from within record parsers
""" """
try: try:
record_id = read_uint(stream) record_id = read_uint(stream)
@ -157,8 +147,6 @@ 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:
@ -316,14 +304,9 @@ class OasisLayout:
""" """
Write this object in OASIS fromat to a stream. Write this object in OASIS fromat to a stream.
Args: :param stream: Stream to write to.
stream: Stream to write to. :return: Number of bytes written.
:raises: InvalidDataError if contained records are invalid.
Returns:
Number of bytes written.
Raises:
InvalidDataError: if contained records are invalid.
""" """
modals = Modals() modals = Modals()
@ -340,7 +323,7 @@ class OasisLayout:
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.bstring, refnum).dedup_write(stream, modals) size += sum(records.XName(x.attribute, x.string, 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)
@ -374,24 +357,23 @@ class Cell:
""" """
Representation of an OASIS cell. Representation of an OASIS cell.
Attributes: Properties:
name (Union[NString, int]): name or "CellName reference" number .name NString or int (CellName reference number)
properties (List[records.Property]): Properties of this cell .properties List of records.Property
placements (List[records.Placement]): Placement record objects .placements List of records.Placement
geometry: (List[records.geometry_t]): Geometry record objectes .geometry List of geometry record objectes
""" """
name: Union[NString, int] name = None # type: NString or int
properties: List[records.Property] properties = None # type: List[records.Property]
placements: List[records.Placement] placements = None # type: List[records.Placement]
geometry: List[records.geometry_t] geometry = None # type: List[records.geometry_t]
def __init__(self, name: Union[NString, str, int]): def __init__(self, name: NString or int):
""" """
Args: :param name: NString or int (CellName reference number)
name: `NString` or "CellName reference" number
""" """
self.name = name if isinstance(name, (NString, int)) else NString(name) self.name = name
self.properties = [] self.properties = []
self.placements = [] self.placements = []
self.geometry = [] self.geometry = []
@ -401,15 +383,10 @@ class Cell:
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.
Args: :param stream: Stream to write to.
stream: Stream to write to. :param modals: Modal variables to use for deduplication.
modals: Modal variables to use for deduplication. :return: Number of bytes written.
: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)
@ -422,17 +399,16 @@ 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: int attribute = None # type: int
bstring: bytes bstring = None # type: bytes
def __init__(self, attribute: int, bstring: bytes): def __init__(self, attribute: int, bstring: bytes):
""" """
Args: :param attribute: Attribute number.
attribute: Attribute number. :param bstring: Binary data.
bstring: Binary data.
""" """
self.attribute = attribute self.attribute = attribute
self.bstring = bstring self.bstring = bstring
@ -440,19 +416,16 @@ 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.
Args: :param record: XName record to use.
record: XName record to use. :return: XName object.
Returns:
`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: Dict[int, Type] = { _GEOMETRY = {
19: records.Text, 19: records.Text,
20: records.Rectangle, 20: records.Rectangle,
21: records.Polygon, 21: records.Polygon,

View file

File diff suppressed because it is too large Load diff

View file

@ -1,15 +1,13 @@
#!/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', 'r') as f:
version = f.read().strip()
setup(name='fatamorgana', setup(name='fatamorgana',
version=version, version=fatamorgana.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',
@ -37,6 +35,7 @@ setup(name='fatamorgana',
'gds', 'gds',
], ],
classifiers=[ classifiers=[
'Programming Language :: Python',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
'Development Status :: 3 - Alpha', 'Development Status :: 3 - Alpha',
'Environment :: Other Environment', 'Environment :: Other Environment',
@ -49,13 +48,9 @@ setup(name='fatamorgana',
'Operating System :: Microsoft :: Windows', 'Operating System :: Microsoft :: Windows',
'Topic :: Scientific/Engineering', 'Topic :: Scientific/Engineering',
'Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)', 'Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)',
'Topic :: Software Development :: Libraries :: Python Modules',
], ],
packages=find_packages(), packages=find_packages(),
package_data={
'fatamorgana': ['VERSION',
'py.typed',
],
},
install_requires=[ install_requires=[
'typing', 'typing',
], ],