Compare commits

..

38 commits

Author SHA1 Message Date
86c1e4cd3b bump version to v0.7 2020-05-19 00:52:52 -07:00
fdf5e9f598 enable type checking for downstream 2020-05-19 00:51:40 -07:00
fab80c8517 cosmetic change to code 2020-05-19 00:51:24 -07:00
492d6416db Docstring updates: CTrapezoid info, Polygon+Path point_list description improvement, and better description of where x and y point to 2020-05-19 00:51:10 -07:00
aaef122178 fixup array equality checking in the case where _USE_NUMPY is false but we somehow still get an ndarray
mostly happens during testing
2020-05-19 00:49:42 -07:00
55638fcde5 fix placement rotation (float modulo int was always returning 0??) 2020-05-19 00:48:22 -07:00
99283aaaf0 enable passing in str where an AString or NString is needed. 2020-05-19 00:47:17 -07:00
705926d443 Add UnfilledModalError, records.verify_modal(), and .get_*() methods.
The .get_*() methods are used to verify that we aren't reading from a
pattern with un-filled modals.

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

1
.gitignore vendored
View file

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

View file

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

View file

@ -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
View file

@ -0,0 +1 @@
0.7

View file

@ -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__

File diff suppressed because it is too large Load diff

View file

@ -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
Args:
stream: Stream to read from.
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.
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
View file

File diff suppressed because it is too large Load diff

View file

@ -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',
],