import 0.2.1
This commit is contained in:
commit
5a4c7db6b4
35 changed files with 3888 additions and 0 deletions
0
gdsii/__init__.py
Normal file
0
gdsii/__init__.py
Normal file
230
gdsii/_records.py
Normal file
230
gdsii/_records.py
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2010 Eugeniy Meshcheryakov <eugen@debian.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
from __future__ import absolute_import
|
||||
from . import record, tags
|
||||
|
||||
class AbstractRecord(object):
|
||||
def __init__(self, variable):
|
||||
self.variable = variable
|
||||
|
||||
def read(self, instance, gen):
|
||||
raise NotImplementedError
|
||||
|
||||
def save(self, instance, stream):
|
||||
raise NotImplementedError
|
||||
|
||||
def __repr__(self):
|
||||
return '<property: %s>'%self.variable
|
||||
|
||||
class SecondVar(object):
|
||||
"""Class that simplifies second property support."""
|
||||
def __init__(self, variable2):
|
||||
self.variable2 = variable2
|
||||
|
||||
class SimpleRecord(AbstractRecord):
|
||||
def __init__(self, variable, gds_record):
|
||||
AbstractRecord.__init__(self, variable)
|
||||
self.gds_record = gds_record
|
||||
|
||||
def read(self, instance, gen):
|
||||
rec = gen.current
|
||||
rec.check_tag(self.gds_record)
|
||||
rec.check_size(1)
|
||||
setattr(instance, self.variable, rec.data[0])
|
||||
gen.read_next()
|
||||
|
||||
def save(self, instance, stream):
|
||||
record.Record(self.gds_record, (getattr(instance, self.variable),)).save(stream)
|
||||
|
||||
class SimpleOptionalRecord(SimpleRecord):
|
||||
def optional_read(self, instance, unused_gen, rec):
|
||||
"""
|
||||
Called when optional tag is found. `rec` contains that tag.
|
||||
`gen` is advanced to next record befor calling this function.
|
||||
"""
|
||||
rec.check_size(1)
|
||||
setattr(instance, self.variable, rec.data[0])
|
||||
|
||||
def read(self, instance, gen):
|
||||
rec = gen.current
|
||||
if rec.tag == self.gds_record:
|
||||
gen.read_next()
|
||||
self.optional_read(instance, gen, rec)
|
||||
|
||||
def save(self, instance, stream):
|
||||
data = getattr(instance, self.variable, None)
|
||||
if data is not None:
|
||||
record.Record(self.gds_record, (data,)).save(stream)
|
||||
|
||||
class OptionalWholeRecord(SimpleOptionalRecord):
|
||||
"""Class for records that need to store all data (not data[0])."""
|
||||
def optional_read(self, instance, unused_gen, rec):
|
||||
setattr(instance, self.variable, rec.data)
|
||||
|
||||
def save(self, instance, stream):
|
||||
data = getattr(instance, self.variable, None)
|
||||
if data is not None:
|
||||
record.Record(self.gds_record, data).save(stream)
|
||||
|
||||
class PropertiesRecord(AbstractRecord):
|
||||
def read(self, instance, gen):
|
||||
rec = gen.current
|
||||
props = []
|
||||
while rec.tag == tags.PROPATTR:
|
||||
rec.check_size(1)
|
||||
propattr = rec.data[0]
|
||||
rec = gen.read_next()
|
||||
rec.check_tag(tags.PROPVALUE)
|
||||
props.append((propattr, rec.data))
|
||||
rec = gen.read_next()
|
||||
setattr(instance, self.variable, props)
|
||||
|
||||
def save(self, instance, stream):
|
||||
props = getattr(instance, self.variable)
|
||||
if props:
|
||||
for (propattr, propvalue) in props:
|
||||
record.Record(tags.PROPATTR, (propattr,)).save(stream)
|
||||
record.Record(tags.PROPVALUE, propvalue).save(stream)
|
||||
|
||||
class XYRecord(SimpleRecord):
|
||||
def read(self, instance, gen):
|
||||
rec = gen.current
|
||||
rec.check_tag(self.gds_record)
|
||||
setattr(instance, self.variable, rec.points)
|
||||
gen.read_next()
|
||||
|
||||
def save(self, instance, stream):
|
||||
pts = getattr(instance, self.variable)
|
||||
record.Record(self.gds_record, points=pts).save(stream)
|
||||
|
||||
class StringRecord(SimpleRecord):
|
||||
def read(self, instance, gen):
|
||||
rec = gen.current
|
||||
rec.check_tag(self.gds_record)
|
||||
setattr(instance, self.variable, rec.data)
|
||||
gen.read_next()
|
||||
|
||||
def save(self, instance, stream):
|
||||
record.Record(self.gds_record, getattr(instance, self.variable)).save(stream)
|
||||
|
||||
class ColRowRecord(AbstractRecord, SecondVar):
|
||||
def __init__(self, variable1, variable2):
|
||||
AbstractRecord.__init__(self, variable1)
|
||||
SecondVar.__init__(self, variable2)
|
||||
|
||||
def read(self, instance, gen):
|
||||
rec = gen.current
|
||||
rec.check_tag(tags.COLROW)
|
||||
rec.check_size(2)
|
||||
cols, rows = rec.data
|
||||
setattr(instance, self.variable, cols)
|
||||
setattr(instance, self.variable2, rows)
|
||||
gen.read_next()
|
||||
|
||||
def save(self, instance, stream):
|
||||
col = getattr(instance, self.variable)
|
||||
row = getattr(instance, self.variable2)
|
||||
record.Record(tags.COLROW, (col, row)).save(stream)
|
||||
|
||||
class TimestampsRecord(SimpleRecord, SecondVar):
|
||||
def __init__(self, variable1, variable2, gds_record):
|
||||
SimpleRecord.__init__(self, variable1, gds_record)
|
||||
SecondVar.__init__(self, variable2)
|
||||
|
||||
def read(self, instance, gen):
|
||||
rec = gen.current
|
||||
rec.check_tag(self.gds_record)
|
||||
mod_time, acc_time = rec.times
|
||||
setattr(instance, self.variable, mod_time)
|
||||
setattr(instance, self.variable2, acc_time)
|
||||
gen.read_next()
|
||||
|
||||
def save(self, instance, stream):
|
||||
mod_time = getattr(instance, self.variable)
|
||||
acc_time = getattr(instance, self.variable2)
|
||||
record.Record(self.gds_record, times=(mod_time, acc_time)).save(stream)
|
||||
|
||||
class STransRecord(OptionalWholeRecord):
|
||||
mag = SimpleOptionalRecord('mag', tags.MAG)
|
||||
angle = SimpleOptionalRecord('angle', tags.ANGLE)
|
||||
|
||||
def optional_read(self, instance, gen, rec):
|
||||
setattr(instance, self.variable, rec.data)
|
||||
self.mag.read(instance, gen)
|
||||
self.angle.read(instance, gen)
|
||||
|
||||
def save(self, instance, stream):
|
||||
data = getattr(instance, self.variable, None)
|
||||
if data is not None:
|
||||
OptionalWholeRecord.save(self, instance, stream)
|
||||
self.mag.save(instance, stream)
|
||||
self.angle.save(instance, stream)
|
||||
|
||||
class ACLRecord(SimpleOptionalRecord):
|
||||
def optional_read(self, instance, unused_gen, rec):
|
||||
setattr(instance, self.variable, rec.acls)
|
||||
|
||||
def save(self, instance, stream):
|
||||
data = getattr(instance, self.variable, None)
|
||||
if data:
|
||||
record.Record(self.gds_record, acls=data).save(stream)
|
||||
|
||||
class FormatRecord(SimpleOptionalRecord, SecondVar):
|
||||
def __init__(self, variable1, variable2, gds_record):
|
||||
SimpleOptionalRecord.__init__(self, variable1, gds_record)
|
||||
SecondVar.__init__(self, variable2)
|
||||
|
||||
def optional_read(self, instance, gen, rec):
|
||||
SimpleOptionalRecord.optional_read(self, instance, gen, rec)
|
||||
cur_rec = gen.curent
|
||||
if cur_rec.tag == tags.MASK:
|
||||
masks = []
|
||||
while cur_rec.tag == tags.MASK:
|
||||
masks.append(cur_rec.data)
|
||||
cur_rec = gen.read_next()
|
||||
cur_rec.check_tag(tags.ENDMASKS)
|
||||
setattr(instance, self.variable2, masks)
|
||||
gen.read_next()
|
||||
|
||||
def save(self, instance, stream):
|
||||
fmt = getattr(instance, self.variable, None)
|
||||
if fmt is not None:
|
||||
SimpleOptionalRecord.save(self, instance, stream)
|
||||
masks = getattr(instance, self.variable2, None)
|
||||
if masks:
|
||||
for mask in masks:
|
||||
record.Record(tags.MASK, mask).save(stream)
|
||||
record.Record(tags.ENDMASKS).save(stream)
|
||||
|
||||
class UnitsRecord(SimpleRecord, SecondVar):
|
||||
def __init__(self, variable1, variable2, gds_record):
|
||||
SimpleRecord.__init__(self, variable1, gds_record)
|
||||
SecondVar.__init__(self, variable2)
|
||||
|
||||
def read(self, instance, gen):
|
||||
rec = gen.current
|
||||
rec.check_tag(self.gds_record)
|
||||
rec.check_size(2)
|
||||
unit1, unit2 = rec.data
|
||||
setattr(instance, self.variable, unit1)
|
||||
setattr(instance, self.variable2, unit2)
|
||||
gen.read_next()
|
||||
|
||||
def save(self, instance, stream):
|
||||
unit1 = getattr(instance, self.variable)
|
||||
unit2 = getattr(instance, self.variable2)
|
||||
record.Record(self.gds_record, (unit1, unit2)).save(stream)
|
||||
390
gdsii/elements.py
Normal file
390
gdsii/elements.py
Normal file
|
|
@ -0,0 +1,390 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2010 Eugeniy Meshcheryakov <eugen@debian.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
:mod:`gdsii.elements` -- interface to GDSII elements
|
||||
====================================================
|
||||
|
||||
This module contains definitions for classes representing
|
||||
various GDSII elements. Mapping between GDSII elements and
|
||||
classes is given in the following table:
|
||||
|
||||
+-------------------+-------------------+
|
||||
| GDSII Record | Class |
|
||||
+===================+===================+
|
||||
| :const:`AREF` | :class:`ARef` |
|
||||
+-------------------+-------------------+
|
||||
| :const:`BOUNDARY` | :class:`Boundary` |
|
||||
+-------------------+-------------------+
|
||||
| :const:`BOX` | :class:`Box` |
|
||||
+-------------------+-------------------+
|
||||
| :const:`NODE` | :class:`Node` |
|
||||
+-------------------+-------------------+
|
||||
| :const:`PATH` | :class:`Path` |
|
||||
+-------------------+-------------------+
|
||||
| :const:`SREF` | :class:`SRef` |
|
||||
+-------------------+-------------------+
|
||||
| :const:`TEXT` | :class:`Text` |
|
||||
+-------------------+-------------------+
|
||||
|
||||
This module implements the following GDS syntax:
|
||||
.. productionlist::
|
||||
element: `aref` |
|
||||
: `boundary` |
|
||||
: `box` |
|
||||
: `node` |
|
||||
: `path` |
|
||||
: `sref` |
|
||||
: `text`
|
||||
Additional definitions:
|
||||
.. productionlist::
|
||||
properties: `property`*
|
||||
property: PROPATTR
|
||||
: PROPVALUE
|
||||
strans: STRANS
|
||||
: [MAG]
|
||||
: [ANGLE]
|
||||
|
||||
.. moduleauthor:: Eugeniy Meshcheryakov <eugen@debian.org>
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from . import exceptions, record, tags, _records
|
||||
|
||||
__all__ = (
|
||||
'Boundary',
|
||||
'Path',
|
||||
'SRef',
|
||||
'ARef',
|
||||
'Text',
|
||||
'Node',
|
||||
'Box'
|
||||
)
|
||||
|
||||
_ELFLAGS = _records.OptionalWholeRecord('elflags', tags.ELFLAGS)
|
||||
_PLEX = _records.SimpleOptionalRecord('plex', tags.PLEX)
|
||||
_LAYER = _records.SimpleRecord('layer', tags.LAYER)
|
||||
_DATATYPE = _records.SimpleRecord('data_type', tags.DATATYPE)
|
||||
_PATHTYPE = _records.SimpleOptionalRecord('path_type', tags.PATHTYPE)
|
||||
_WIDTH = _records.SimpleOptionalRecord('width', tags.WIDTH)
|
||||
_BGNEXTN = _records.SimpleOptionalRecord('bgn_extn', tags.BGNEXTN)
|
||||
_ENDEXTN = _records.SimpleOptionalRecord('end_extn', tags.ENDEXTN)
|
||||
_XY = _records.XYRecord('xy', tags.XY)
|
||||
_SNAME = _records.StringRecord('struct_name', tags.SNAME)
|
||||
_STRANS = _records.STransRecord('strans', tags.STRANS)
|
||||
_COLROW = _records.ColRowRecord('cols', 'rows')
|
||||
_TEXTTYPE = _records.SimpleRecord('text_type', tags.TEXTTYPE)
|
||||
_PRESENTATION = _records.OptionalWholeRecord('presentation', tags.PRESENTATION)
|
||||
_STRING = _records.StringRecord('string', tags.STRING)
|
||||
_NODETYPE = _records.SimpleRecord('node_type', tags.NODETYPE)
|
||||
_BOXTYPE = _records.SimpleRecord('box_type', tags.BOXTYPE)
|
||||
_PROPERTIES = _records.PropertiesRecord('properties')
|
||||
|
||||
class _Base(object):
|
||||
"""Base class for all GDSII elements."""
|
||||
|
||||
# dummy descriptors to silence pyckecker, should be set in derived classes
|
||||
_gds_tag = None
|
||||
_gds_objs = None
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the element."""
|
||||
self._init_optional()
|
||||
|
||||
def _init_optional(self):
|
||||
"""Initialize optional attributes to None."""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def _load(cls, gen):
|
||||
"""
|
||||
Load an element from file using given generator `gen`.
|
||||
|
||||
:param gen: :class:`pygdsii.record.Record` generator
|
||||
:returns: new element of class defined by `gen`
|
||||
"""
|
||||
element_class = cls._tag_to_class_map[gen.current.tag]
|
||||
if not element_class:
|
||||
raise exceptions.FormatError('unexpected element tag')
|
||||
# do not call __init__() during reading from file
|
||||
# __init__() should require some arguments
|
||||
new_element = element_class._read_element(gen)
|
||||
return new_element
|
||||
|
||||
@classmethod
|
||||
def _read_element(cls, gen):
|
||||
"""Read element using `gen` generator."""
|
||||
self = cls.__new__(cls)
|
||||
self._init_optional()
|
||||
gen.read_next()
|
||||
for obj in self._gds_objs:
|
||||
obj.read(self, gen)
|
||||
gen.current.check_tag(tags.ENDEL)
|
||||
gen.read_next()
|
||||
return self
|
||||
|
||||
def _save(self, stream):
|
||||
record.Record(self._gds_tag).save(stream)
|
||||
for obj in self._gds_objs:
|
||||
obj.save(self, stream)
|
||||
record.Record(tags.ENDEL).save(stream)
|
||||
|
||||
class Boundary(_Base):
|
||||
"""
|
||||
Class for :const:`BOUNDARY` GDSII element.
|
||||
|
||||
GDS syntax:
|
||||
.. productionlist::
|
||||
boundary: BOUNDARY
|
||||
: [ELFLAGS]
|
||||
: [PLEX]
|
||||
: LAYER
|
||||
: DATATYPE
|
||||
: XY
|
||||
: [`properties`]
|
||||
: ENDEL
|
||||
"""
|
||||
_gds_tag = tags.BOUNDARY
|
||||
_gds_objs = (_ELFLAGS, _PLEX, _LAYER, _DATATYPE, _XY, _PROPERTIES)
|
||||
__slots__ = ('layer', 'data_type', 'xy', 'elflags', 'plex', 'properties')
|
||||
|
||||
def __init__(self, layer, data_type, xy):
|
||||
_Base.__init__(self)
|
||||
self.layer = layer
|
||||
self.data_type = data_type
|
||||
self.xy = xy
|
||||
|
||||
def _init_optional(self):
|
||||
self.elflags = None
|
||||
self.plex = None
|
||||
self.properties = None
|
||||
|
||||
class Path(_Base):
|
||||
"""
|
||||
Class for :const:`PATH` GDSII element.
|
||||
|
||||
GDS syntax:
|
||||
.. productionlist::
|
||||
path: PATH
|
||||
: [ELFLAGS]
|
||||
: [PLEX]
|
||||
: LAYER
|
||||
: DATATYPE
|
||||
: [PATHTYPE]
|
||||
: [WIDTH]
|
||||
: [BGNEXTN]
|
||||
: [ENDEXTN]
|
||||
: XY
|
||||
: [`properties`]
|
||||
: ENDEL
|
||||
"""
|
||||
_gds_tag = tags.PATH
|
||||
_gds_objs = (_ELFLAGS, _PLEX, _LAYER, _DATATYPE, _PATHTYPE, _WIDTH,
|
||||
_BGNEXTN, _ENDEXTN, _XY, _PROPERTIES)
|
||||
__slots__ = ('layer', 'data_type', 'xy', 'elflags', 'plex', 'path_type',
|
||||
'width', 'bgn_extn', 'end_extn', 'properties')
|
||||
|
||||
def __init__(self, layer, data_type, xy):
|
||||
_Base.__init__(self)
|
||||
self.layer = layer
|
||||
self.data_type = data_type
|
||||
self.xy = xy
|
||||
|
||||
def _init_optional(self):
|
||||
self.elflags = None
|
||||
self.plex = None
|
||||
self.path_type = None
|
||||
self.width = None
|
||||
self.bgn_extn = None
|
||||
self.end_extn = None
|
||||
self.properties = None
|
||||
|
||||
class SRef(_Base):
|
||||
"""
|
||||
Class for :const:`SREF` GDSII element.
|
||||
|
||||
GDS syntax:
|
||||
.. productionlist::
|
||||
sref: SREF
|
||||
: [ELFLAGS]
|
||||
: [PLEX]
|
||||
: SNAME
|
||||
: [`strans`]
|
||||
: XY
|
||||
: [`properties`]
|
||||
: ENDEL
|
||||
"""
|
||||
_gds_tag = tags.SREF
|
||||
_gds_objs = (_ELFLAGS, _PLEX, _SNAME, _STRANS, _XY, _PROPERTIES)
|
||||
__slots__ = ('struct_name', 'xy', 'elflags', 'strans', 'mag', 'angle',
|
||||
'properties')
|
||||
|
||||
def __init__(self, struct_name, xy):
|
||||
_Base.__init__(self)
|
||||
self.struct_name = struct_name
|
||||
self.xy = xy
|
||||
|
||||
def _init_optional(self):
|
||||
self.elflags = None
|
||||
self.strans = None
|
||||
self.mag = None
|
||||
self.angle = None
|
||||
self.properties = None
|
||||
|
||||
class ARef(_Base):
|
||||
"""
|
||||
Class for :const:`AREF` GDSII element.
|
||||
|
||||
GDS syntax:
|
||||
.. productionlist::
|
||||
aref: AREF
|
||||
: [ELFLAGS]
|
||||
: [PLEX]
|
||||
: SNAME
|
||||
: [`strans`]
|
||||
: COLROW
|
||||
: XY
|
||||
: [`properties`]
|
||||
: ENDEL
|
||||
"""
|
||||
_gds_tag = tags.AREF
|
||||
_gds_objs = (_ELFLAGS, _PLEX, _SNAME, _STRANS, _COLROW, _XY, _PROPERTIES)
|
||||
__slots__ = ('struct_name', 'cols', 'rows', 'xy', 'elflags', 'plex',
|
||||
'strans', 'mag', 'angle', 'properties')
|
||||
|
||||
def __init__(self, struct_name, cols, rows, xy):
|
||||
_Base.__init__(self)
|
||||
self.struct_name = struct_name
|
||||
self.cols = cols
|
||||
self.rows = rows
|
||||
self.xy = xy
|
||||
|
||||
def _init_optional(self):
|
||||
self.elflags = None
|
||||
self.plex = None
|
||||
self.strans = None
|
||||
self.mag = None
|
||||
self.angle = None
|
||||
self.properties = None
|
||||
|
||||
class Text(_Base):
|
||||
"""
|
||||
Class for :const:`TEXT` GDSII element.
|
||||
|
||||
GDS syntax:
|
||||
.. productionlist::
|
||||
text: TEXT
|
||||
: [ELFLAGS]
|
||||
: [PLEX]
|
||||
: LAYER
|
||||
: TEXTTYPE
|
||||
: [PRESENTATION]
|
||||
: [PATHTYPE]
|
||||
: [WIDTH]
|
||||
: [`strans`]
|
||||
: XY
|
||||
: STRING
|
||||
: [`properties`]
|
||||
: ENDEL
|
||||
"""
|
||||
_gds_tag = tags.TEXT
|
||||
_gds_objs = (_ELFLAGS, _PLEX, _LAYER, _TEXTTYPE, _PRESENTATION, _PATHTYPE,
|
||||
_WIDTH, _STRANS, _XY, _STRING, _PROPERTIES)
|
||||
__slots__ = ('layer', 'text_type', 'xy', 'string', 'elflags', 'plex',
|
||||
'presentation', 'path_type', 'width', 'strans', 'mag', 'angle',
|
||||
'properties')
|
||||
|
||||
def __init__(self, layer, text_type, xy, string):
|
||||
_Base.__init__(self)
|
||||
self.layer = layer
|
||||
self.text_type = text_type
|
||||
self.xy = xy
|
||||
self.string = string
|
||||
|
||||
def _init_optional(self):
|
||||
self.elflags = None
|
||||
self.plex = None
|
||||
self.presentation = None
|
||||
self.path_type = None
|
||||
self.width = None
|
||||
self.strans = None
|
||||
self.mag = None
|
||||
self.angle = None
|
||||
self.properties = None
|
||||
|
||||
class Node(_Base):
|
||||
"""
|
||||
Class for :const:`NODE` GDSII element.
|
||||
|
||||
GDS syntax:
|
||||
.. productionlist::
|
||||
node: NODE
|
||||
: [ELFLAGS]
|
||||
: [PLEX]
|
||||
: LAYER
|
||||
: NODETYPE
|
||||
: XY
|
||||
: [`properties`]
|
||||
: ENDEL
|
||||
"""
|
||||
_gds_tag = tags.NODE
|
||||
_gds_objs = (_ELFLAGS, _PLEX, _LAYER, _NODETYPE, _XY, _PROPERTIES)
|
||||
__slots__ = ('layer', 'node_type', 'xy', 'elflags', 'plex', 'properties')
|
||||
|
||||
def __init__(self, layer, node_type, xy):
|
||||
_Base.__init__(self)
|
||||
self.layer = layer
|
||||
self.node_type = node_type
|
||||
self.xy = xy
|
||||
|
||||
def _init_optional(self):
|
||||
self.elflags = None
|
||||
self.plex = None
|
||||
self.properties = None
|
||||
|
||||
class Box(_Base):
|
||||
"""
|
||||
Class for :const:`BOX` GDSII element.
|
||||
|
||||
GDS syntax:
|
||||
.. productionlist::
|
||||
box: BOX
|
||||
: [ELFLAGS]
|
||||
: [PLEX]
|
||||
: LAYER
|
||||
: BOXTYPE
|
||||
: XY
|
||||
: [`properties`]
|
||||
: ENDEL
|
||||
"""
|
||||
_gds_tag = tags.BOX
|
||||
_gds_objs = (_ELFLAGS, _PLEX, _LAYER, _BOXTYPE, _XY, _PROPERTIES)
|
||||
__slots__ = ('layer', 'box_type', 'xy', 'elflags', 'plex', 'properties')
|
||||
|
||||
def __init__(self, layer, box_type, xy):
|
||||
_Base.__init__(self)
|
||||
self.layer = layer
|
||||
self.box_type = box_type
|
||||
self.xy = xy
|
||||
|
||||
def _init_optional(self):
|
||||
self.elflags = None
|
||||
self.plex = None
|
||||
self.properties = None
|
||||
|
||||
_all_elements = (Boundary, Path, SRef, ARef, Text, Node, Box)
|
||||
|
||||
_Base._tag_to_class_map = (lambda: dict(((cls._gds_tag, cls) for cls in _all_elements)))()
|
||||
42
gdsii/exceptions.py
Normal file
42
gdsii/exceptions.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2010 Eugeniy Meshcheryakov <eugen@debian.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
:mod:`gdsii.exceptions` --- GDSII exceptions
|
||||
============================================
|
||||
|
||||
This module contains exception classes used in `python-gdsii`.
|
||||
"""
|
||||
__all__ = ('FormatError', 'EndOfFileError', 'IncorrectDataSize',
|
||||
'UnsupportedTagType', 'MissingRecord', 'DataSizeError')
|
||||
|
||||
class FormatError(Exception):
|
||||
"""Base class for all GDSII exceptions."""
|
||||
|
||||
class EndOfFileError(FormatError):
|
||||
"""Raised on unexpected end of file."""
|
||||
|
||||
class IncorrectDataSize(FormatError):
|
||||
"""Raised if data size is incorrect."""
|
||||
|
||||
class UnsupportedTagType(FormatError):
|
||||
"""Raised on unsupported tag type."""
|
||||
|
||||
class MissingRecord(FormatError):
|
||||
"""Raised when required record is not found."""
|
||||
|
||||
class DataSizeError(FormatError):
|
||||
"""Raised when data size is incorrect for a given record."""
|
||||
139
gdsii/library.py
Normal file
139
gdsii/library.py
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2010 Eugeniy Meshcheryakov <eugen@debian.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
:mod:`gdsii.library` --- interface to a GDSII library
|
||||
=====================================================
|
||||
|
||||
This module contains class that represents a GDSII library.
|
||||
|
||||
.. moduleauthor:: Eugeniy Meshcheryakov <eugen@debian.org>
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from . import exceptions, record, structure, tags, _records
|
||||
from datetime import datetime
|
||||
|
||||
_HEADER = _records.SimpleRecord('version', tags.HEADER)
|
||||
_BGNLIB = _records.TimestampsRecord('mod_time', 'acc_time', tags.BGNLIB)
|
||||
_LIBDIRSIZE = _records.SimpleOptionalRecord('libdirsize', tags.LIBDIRSIZE)
|
||||
_SRFNAME = _records.OptionalWholeRecord('srfname', tags.SRFNAME)
|
||||
_LIBSECUR = _records.ACLRecord('acls', tags.LIBSECUR)
|
||||
_LIBNAME = _records.StringRecord('name', tags.LIBNAME)
|
||||
_REFLIBS = _records.OptionalWholeRecord('reflibs', tags.REFLIBS)
|
||||
_FONTS = _records.OptionalWholeRecord('fonts', tags.FONTS)
|
||||
_ATTRTABLE = _records.OptionalWholeRecord('attrtable', tags.ATTRTABLE)
|
||||
_GENERATIONS = _records.SimpleOptionalRecord('generations', tags.GENERATIONS)
|
||||
_FORMAT = _records.FormatRecord('format', 'masks', tags.FORMAT)
|
||||
_UNITS = _records.UnitsRecord('logical_unit', 'physical_unit', tags.UNITS)
|
||||
|
||||
class Library(list):
|
||||
"""
|
||||
GDSII library class. This class is derived from :class:`list` and can contain
|
||||
one one more instances of :class:`gdsii.structure.Structure`.
|
||||
|
||||
GDS syntax for the library:
|
||||
.. productionlist::
|
||||
library: HEADER
|
||||
: BGNLIB
|
||||
: [LIBDIRSIZE]
|
||||
: [SRFNAME]
|
||||
: [LIBSECUR]
|
||||
: LIBNAME
|
||||
: [REFLIBS]
|
||||
: [FONTS]
|
||||
: [ATTRTABLE]
|
||||
: [GENERATIONS]
|
||||
: [`format`]
|
||||
: UNITS
|
||||
: {`structure`}*
|
||||
: ENDLIB
|
||||
format: FORMAT
|
||||
: [MASK+ ENDMASKS]
|
||||
"""
|
||||
_gds_objs = (_HEADER, _BGNLIB, _LIBDIRSIZE, _SRFNAME, _LIBSECUR, _LIBNAME, _REFLIBS,
|
||||
_FONTS, _ATTRTABLE, _GENERATIONS, _FORMAT, _UNITS)
|
||||
|
||||
def __init__(self, version, name, physical_unit, logical_unit, mod_time=None,
|
||||
acc_time=None):
|
||||
"""
|
||||
Initialize the library.
|
||||
`mod_time` and `acc_time` are set to current UTC time by default.
|
||||
"""
|
||||
list.__init__(self)
|
||||
self.version = version
|
||||
self.name = name
|
||||
self.physical_unit = physical_unit
|
||||
self.logical_unit = logical_unit
|
||||
self.mod_time = mod_time if mod_time is not None else datetime.utcnow()
|
||||
self.acc_time = acc_time if acc_time is not None else datetime.utcnow()
|
||||
self._init_optional()
|
||||
|
||||
def _init_optional(self):
|
||||
"""Initialize optional attributes to None."""
|
||||
self.libdirsize = None
|
||||
self.srfname = None
|
||||
self.acls = None
|
||||
self.reflibs = None
|
||||
self.fonts = None
|
||||
self.attrtable = None
|
||||
self.generations = None
|
||||
self.format = None
|
||||
self.masks = None
|
||||
|
||||
@classmethod
|
||||
def load(cls, stream):
|
||||
"""
|
||||
Load a GDS library from a file.
|
||||
|
||||
:param stream: a :class:`file` or file-like object opened for reading in binary mode.
|
||||
:returns: a new library.
|
||||
"""
|
||||
self = cls.__new__(cls)
|
||||
list.__init__(self)
|
||||
self._init_optional()
|
||||
|
||||
gen = record.Reader(stream)
|
||||
|
||||
gen.read_next()
|
||||
for obj in self._gds_objs:
|
||||
obj.read(self, gen)
|
||||
|
||||
# read structures starting with BGNSTR or ENDLIB
|
||||
rec = gen.current
|
||||
while True:
|
||||
if rec.tag == tags.BGNSTR:
|
||||
self.append(structure.Structure._load(gen))
|
||||
rec = gen.read_next()
|
||||
elif rec.tag == tags.ENDLIB:
|
||||
break
|
||||
else:
|
||||
raise exceptions.FormatError('unexpected tag where BGNSTR or ENDLIB are expected: %d', rec.tag)
|
||||
return self
|
||||
|
||||
def save(self, stream):
|
||||
"""
|
||||
Save the library into a file.
|
||||
|
||||
:param stream: a :class:`file` or file-like object opened for writing in binary mode.
|
||||
"""
|
||||
for obj in self._gds_objs:
|
||||
obj.save(self, stream)
|
||||
for struc in self:
|
||||
struc._save(stream)
|
||||
record.Record(tags.ENDLIB).save(stream)
|
||||
|
||||
def __repr__(self):
|
||||
return '<Library: %s>' % self.name.decode()
|
||||
578
gdsii/record.py
Normal file
578
gdsii/record.py
Normal file
|
|
@ -0,0 +1,578 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2010 Eugeniy Meshcheryakov <eugen@debian.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
:mod:`gdsii.record` --- GDSII record I/O
|
||||
========================================
|
||||
|
||||
This module contains classes for low-level GDSII I/O.
|
||||
|
||||
.. moduleauthor:: Eugeniy Meshcheryakov <eugen@debian.org>
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from . import exceptions, tags, types
|
||||
from datetime import datetime
|
||||
import math
|
||||
import struct
|
||||
|
||||
__all__ = [
|
||||
'Record',
|
||||
'Reader'
|
||||
]
|
||||
|
||||
_RECORD_HEADER_FMT = struct.Struct('>HH')
|
||||
|
||||
def _parse_nodata(data):
|
||||
"""Parse :const:`NODATA` data type. Does nothing."""
|
||||
|
||||
def _parse_bitarray(data):
|
||||
"""
|
||||
Parse :const:`BITARRAY` data type.
|
||||
|
||||
>>> _parse_bitarray(b'ab') # ok, 2 bytes
|
||||
24930
|
||||
>>> _parse_bitarray(b'abcd') # too long
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IncorrectDataSize: BITARRAY
|
||||
>>> _parse_bitarray('') # zero bytes
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IncorrectDataSize: BITARRAY
|
||||
"""
|
||||
if len(data) != 2:
|
||||
raise exceptions.IncorrectDataSize('BITARRAY')
|
||||
(val,) = struct.unpack('>H', data)
|
||||
return val
|
||||
|
||||
def _parse_int2(data):
|
||||
"""
|
||||
Parse INT2 data type.
|
||||
|
||||
>>> _parse_int2(b'abcd') # ok, even number of bytes
|
||||
(24930, 25444)
|
||||
>>> _parse_int2(b'abcde') # odd number of bytes
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IncorrectDataSize: INT2
|
||||
>>> _parse_int2(b'') # zero bytes
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IncorrectDataSize: INT2
|
||||
"""
|
||||
data_len = len(data)
|
||||
if not data_len or (data_len % 2):
|
||||
raise exceptions.IncorrectDataSize('INT2')
|
||||
return struct.unpack('>%dh' % (data_len//2), data)
|
||||
|
||||
def _parse_int4(data):
|
||||
"""
|
||||
Parse INT4 data type.
|
||||
|
||||
>>> _parse_int4(b'abcd')
|
||||
(1633837924,)
|
||||
>>> _parse_int4(b'abcdef') # not divisible by 4
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IncorrectDataSize: INT4
|
||||
>>> _parse_int4(b'') # zero bytes
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IncorrectDataSize: INT4
|
||||
"""
|
||||
data_len = len(data)
|
||||
if not data_len or (data_len % 4):
|
||||
raise exceptions.IncorrectDataSize('INT4')
|
||||
return struct.unpack('>%dl' % (data_len//4), data)
|
||||
|
||||
def _int_to_real(num):
|
||||
"""
|
||||
Convert REAL8 from internal integer representation to Python reals.
|
||||
|
||||
Zeroes:
|
||||
>>> print(_int_to_real(0x0))
|
||||
0.0
|
||||
>>> print(_int_to_real(0x8000000000000000)) # negative
|
||||
0.0
|
||||
>>> print(_int_to_real(0xff00000000000000)) # denormalized
|
||||
0.0
|
||||
|
||||
Others:
|
||||
>>> print(_int_to_real(0x4110000000000000))
|
||||
1.0
|
||||
>>> print(_int_to_real(0xC120000000000000))
|
||||
-2.0
|
||||
"""
|
||||
sgn = -1 if 0x8000000000000000 & num else 1
|
||||
mant = num & 0x00ffffffffffffff
|
||||
exp = (num >> 56) & 0x7f
|
||||
return math.ldexp(sgn * mant, 4 * (exp - 64) - 56)
|
||||
|
||||
def _parse_real8(data):
|
||||
"""
|
||||
Parse REAL8 data type.
|
||||
|
||||
>>> _parse_real8(struct.pack('>3Q', 0x0, 0x4110000000000000, 0xC120000000000000))
|
||||
(0.0, 1.0, -2.0)
|
||||
>>> _parse_real8(b'') # zero bytes
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IncorrectDataSize: REAL8
|
||||
>>> _parse_real8(b'abcd') # not divisible by 8
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IncorrectDataSize: REAL8
|
||||
"""
|
||||
data_len = len(data)
|
||||
if not data_len or (data_len % 8):
|
||||
raise exceptions.IncorrectDataSize('REAL8')
|
||||
ints = struct.unpack('>%dQ' % (data_len//8), data)
|
||||
return tuple(_int_to_real(n) for n in ints)
|
||||
|
||||
def _parse_ascii(data):
|
||||
r"""
|
||||
Parse ASCII data type.
|
||||
|
||||
>>> _parse_ascii(b'') # zero bytes
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IncorrectDataSize: ASCII
|
||||
>>> _parse_ascii(b'abcde') == b'abcde'
|
||||
True
|
||||
>>> _parse_ascii(b'abcde\0') == b'abcde' # strips trailing NUL
|
||||
True
|
||||
"""
|
||||
if not len(data):
|
||||
raise exceptions.IncorrectDataSize('ASCII')
|
||||
# XXX cross-version compatibility
|
||||
if data[-1:] == b'\0':
|
||||
return data[:-1]
|
||||
return data
|
||||
|
||||
_PARSE_FUNCS = {
|
||||
types.NODATA: _parse_nodata,
|
||||
types.BITARRAY: _parse_bitarray,
|
||||
types.INT2: _parse_int2,
|
||||
types.INT4: _parse_int4,
|
||||
types.REAL8: _parse_real8,
|
||||
types.ASCII: _parse_ascii
|
||||
}
|
||||
|
||||
def _pack_nodata(data):
|
||||
"""
|
||||
Pack NODATA tag data. Should always return empty string::
|
||||
|
||||
>>> packed = _pack_nodata([])
|
||||
>>> packed == b''
|
||||
True
|
||||
>>> len(packed)
|
||||
0
|
||||
"""
|
||||
return b''
|
||||
|
||||
def _pack_bitarray(data):
|
||||
"""
|
||||
Pack BITARRAY tag data.
|
||||
|
||||
>>> packed = _pack_bitarray(123)
|
||||
>>> packed == struct.pack('>H', 123)
|
||||
True
|
||||
>>> len(packed)
|
||||
2
|
||||
"""
|
||||
return struct.pack('>H', data)
|
||||
|
||||
def _pack_int2(data):
|
||||
"""
|
||||
Pack INT2 tag data.
|
||||
|
||||
>>> _pack_int2([1, 2, -3]) == struct.pack('>3h', 1, 2, -3)
|
||||
True
|
||||
>>> packed = _pack_int2((1, 2, 3))
|
||||
>>> packed == struct.pack('>3h', 1, 2, 3)
|
||||
True
|
||||
>>> len(packed)
|
||||
6
|
||||
"""
|
||||
size = len(data)
|
||||
return struct.pack('>{0}h'.format(size), *data)
|
||||
|
||||
def _pack_int4(data):
|
||||
"""
|
||||
Pack INT4 tag data.
|
||||
|
||||
>>> _pack_int4([1, 2, -3]) == struct.pack('>3l', 1, 2, -3)
|
||||
True
|
||||
>>> packed = _pack_int4((1, 2, 3))
|
||||
>>> packed == struct.pack('>3l', 1, 2, 3)
|
||||
True
|
||||
>>> len(packed)
|
||||
12
|
||||
"""
|
||||
size = len(data)
|
||||
return struct.pack('>{0}l'.format(size), *data)
|
||||
|
||||
def _real_to_int(fnum):
|
||||
"""
|
||||
Convert REAL8 from Python real to internal integer representation.
|
||||
|
||||
>>> '0x%016x' % _real_to_int(0.0)
|
||||
'0x0000000000000000'
|
||||
>>> print(_int_to_real(_real_to_int(1.0)))
|
||||
1.0
|
||||
>>> print(_int_to_real(_real_to_int(-2.0)))
|
||||
-2.0
|
||||
>>> print(_int_to_real(_real_to_int(1e-9)))
|
||||
1e-09
|
||||
"""
|
||||
# first convert number to IEEE double and split it in parts
|
||||
(ieee,) = struct.unpack('=Q', struct.pack('=d', fnum))
|
||||
sign = ieee & 0x8000000000000000
|
||||
ieee_exp = (ieee >> 52) & 0x7ff
|
||||
ieee_mant = ieee & 0xfffffffffffff
|
||||
|
||||
if ieee_exp == 0:
|
||||
# zero or denormals
|
||||
# TODO maybe handle denormals
|
||||
return 0
|
||||
|
||||
# substract exponent bias
|
||||
unb_ieee_exp = ieee_exp - 1023
|
||||
# add leading one and move to GDSII position
|
||||
ieee_mant_full = (ieee_mant + 0x10000000000000) << 3
|
||||
|
||||
# convert exponent to 16-based, +1 for differences in presentation
|
||||
# of mantissa (1.xxxx in EEEE and 0.1xxxxx in GDSII
|
||||
exp16, rest = divmod(unb_ieee_exp + 1, 4)
|
||||
# compensate exponent converion
|
||||
if rest:
|
||||
rest = 4 - rest
|
||||
exp16 += 1
|
||||
ieee_mant_comp = ieee_mant_full >> rest
|
||||
|
||||
# add GDSII exponent bias
|
||||
exp16_biased = exp16 + 64
|
||||
|
||||
# try to fit everything
|
||||
if exp16_biased < -14:
|
||||
return 0 # number is too small. FIXME is it possible?
|
||||
elif exp16_biased < 0:
|
||||
ieee_mant_comp = ieee_mant_comp >> (exp16_biased * 4)
|
||||
exp16_biased = 0
|
||||
elif exp16_biased > 0x7f:
|
||||
raise exceptions.FormatError('number is to big for REAL8')
|
||||
|
||||
return sign | (exp16_biased << 56) | ieee_mant_comp
|
||||
|
||||
def _pack_real8(data):
|
||||
"""
|
||||
Pack REAL8 tag data.
|
||||
|
||||
>>> packed = _pack_real8([0, 1, -1, 0.5, 1e-9])
|
||||
>>> len(packed)
|
||||
40
|
||||
>>> list(map(str, _parse_real8(packed)))
|
||||
['0.0', '1.0', '-1.0', '0.5', '1e-09']
|
||||
"""
|
||||
size = len(data)
|
||||
return struct.pack('>{0}Q'.format(size), *[_real_to_int(num) for num in data])
|
||||
|
||||
def _pack_ascii(data):
|
||||
r"""
|
||||
Pack ASCII tag data.
|
||||
|
||||
>>> _pack_ascii(b'abcd') == b'abcd'
|
||||
True
|
||||
>>> _pack_ascii(b'abc') == b'abc\0'
|
||||
True
|
||||
"""
|
||||
size = len(data)
|
||||
if size % 2:
|
||||
return data + b'\0'
|
||||
return data
|
||||
|
||||
_PACK_FUNCS = {
|
||||
types.NODATA: _pack_nodata,
|
||||
types.BITARRAY: _pack_bitarray,
|
||||
types.INT2: _pack_int2,
|
||||
types.INT4: _pack_int4,
|
||||
types.REAL8: _pack_real8,
|
||||
types.ASCII: _pack_ascii
|
||||
}
|
||||
|
||||
class Record(object):
|
||||
"""
|
||||
Class for representing a GDSII record with attached data.
|
||||
Example::
|
||||
|
||||
>>> r = Record(tags.STRNAME, 'my_structure')
|
||||
>>> '%04x' % r.tag
|
||||
'0606'
|
||||
>>> r.tag_name
|
||||
'STRNAME'
|
||||
>>> r.tag_type
|
||||
6
|
||||
>>> r.tag_type_name
|
||||
'ASCII'
|
||||
>>> r.data
|
||||
'my_structure'
|
||||
|
||||
>>> r = Record(0xffff, 'xxx') # Unknown tag type
|
||||
>>> r.tag_name
|
||||
'0xffff'
|
||||
>>> r.tag_type_name
|
||||
'0xff'
|
||||
"""
|
||||
__slots__ = ['tag', 'data']
|
||||
|
||||
def __init__(self, tag, data=None, points=None, times=None, acls=None):
|
||||
"""Initialize with tag and parsed data."""
|
||||
self.tag = tag
|
||||
if data is not None:
|
||||
self.data = data
|
||||
elif points is not None:
|
||||
new_data = []
|
||||
# TODO make it faster
|
||||
for point in points:
|
||||
new_data.append(point[0])
|
||||
new_data.append(point[1])
|
||||
self.data = new_data
|
||||
elif times is not None:
|
||||
mod_time = times[0]
|
||||
acc_time = times[1]
|
||||
self.data = (
|
||||
mod_time.year - 1900,
|
||||
mod_time.month,
|
||||
mod_time.day,
|
||||
mod_time.hour,
|
||||
mod_time.minute,
|
||||
mod_time.second,
|
||||
acc_time.year - 1900,
|
||||
acc_time.month,
|
||||
acc_time.day,
|
||||
acc_time.hour,
|
||||
acc_time.minute,
|
||||
acc_time.second
|
||||
)
|
||||
elif acls is not None:
|
||||
new_data = []
|
||||
for acl in acls:
|
||||
new_data.extend(acl)
|
||||
self.data = new_data
|
||||
else:
|
||||
self.data = None
|
||||
|
||||
def check_tag(self, tag):
|
||||
"""
|
||||
Checks if current record has the same tag as the given one.
|
||||
Raises :exc:`MissingRecord` exception otherwise. For example::
|
||||
|
||||
>>> rec = Record(tags.STRNAME, b'struct')
|
||||
>>> rec.check_tag(tags.STRNAME)
|
||||
>>> rec.check_tag(tags.DATATYPE)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
MissingRecord: Wanted: 3586, got: STRNAME
|
||||
"""
|
||||
if self.tag != tag:
|
||||
raise exceptions.MissingRecord('Wanted: %s, got: %s'%(tag, self.tag_name))
|
||||
|
||||
def check_size(self, size):
|
||||
"""
|
||||
Checks if data size equals to the given size.
|
||||
Raises :exc:`DataSizeError` otherwise. For example::
|
||||
|
||||
>>> rec = Record(tags.DATATYPE, (0,))
|
||||
>>> rec.check_size(1)
|
||||
>>> rec.check_size(5)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
DataSizeError: 3586
|
||||
"""
|
||||
if len(self.data) != size:
|
||||
raise exceptions.DataSizeError(self.tag)
|
||||
|
||||
@classmethod
|
||||
def read(cls, stream):
|
||||
"""
|
||||
Read a GDSII record from file.
|
||||
|
||||
:param stream: GDS file opened for reading in binary mode
|
||||
:returns: a new :class:`Record` instance
|
||||
:raises: :exc:`UnsupportedTagType` if data cannot be parsed
|
||||
:raises: :exc:`EndOfFileError` if end of file is reached
|
||||
"""
|
||||
header = stream.read(4)
|
||||
if not header or len(header) != 4:
|
||||
raise exceptions.EndOfFileError
|
||||
data_size, tag = _RECORD_HEADER_FMT.unpack(header)
|
||||
if data_size < 4:
|
||||
raise exceptions.IncorrectDataSize('data size is too small')
|
||||
if data_size % 2:
|
||||
raise exceptions.IncorrectDataSize('data size is odd')
|
||||
|
||||
data_size -= 4 # substract header size
|
||||
|
||||
data = stream.read(data_size)
|
||||
if len(data) != data_size:
|
||||
raise exceptions.EndOfFileError
|
||||
|
||||
tag_type = tags.type_of_tag(tag)
|
||||
try:
|
||||
parse_func = _PARSE_FUNCS[tag_type]
|
||||
except KeyError:
|
||||
raise exceptions.UnsupportedTagType(tag_type)
|
||||
return cls(tag, parse_func(data))
|
||||
|
||||
def save(self, stream):
|
||||
"""
|
||||
Save record to a GDS file.
|
||||
|
||||
:param stream: file opened for writing in binary mode
|
||||
:raises: :exc:`UnsupportedTagType` if tag type is not supported
|
||||
:raises: :exc:`FormatError` on incorrect data sizes, etc
|
||||
:raises: whatever :func:`struct.pack` can raise
|
||||
"""
|
||||
tag_type = self.tag_type
|
||||
try:
|
||||
pack_func = _PACK_FUNCS[tag_type]
|
||||
except KeyError:
|
||||
raise exceptions.UnsupportedTagType(tag_type)
|
||||
packed_data = pack_func(self.data)
|
||||
record_size = len(packed_data) + 4
|
||||
if record_size > 0xFFFF:
|
||||
raise exceptions.FormatError('data size is too big')
|
||||
header = _RECORD_HEADER_FMT.pack(record_size, self.tag)
|
||||
stream.write(header)
|
||||
stream.write(packed_data)
|
||||
|
||||
@property
|
||||
def tag_name(self):
|
||||
"""Tag name, if known, otherwise tag ID formatted as hex number."""
|
||||
if self.tag in tags.REV_DICT:
|
||||
return tags.REV_DICT[self.tag]
|
||||
return '0x%04x' % self.tag
|
||||
|
||||
@property
|
||||
def tag_type(self):
|
||||
"""Tag data type ID."""
|
||||
return tags.type_of_tag(self.tag)
|
||||
|
||||
@property
|
||||
def tag_type_name(self):
|
||||
"""Tag data type name, if known, and formatted number otherwise."""
|
||||
tag_type = tags.type_of_tag(self.tag)
|
||||
if tag_type in types.REV_DICT:
|
||||
return types.REV_DICT[tag_type]
|
||||
return '0x%02x' % tag_type
|
||||
|
||||
@property
|
||||
def points(self):
|
||||
"""
|
||||
Convert data to list of points. Useful for :const:`XY` record.
|
||||
Raises :exc:`DataSizeError` if data size is incorrect.
|
||||
For example::
|
||||
|
||||
>>> r = Record(tags.XY, [0, 1, 2, 3])
|
||||
>>> r.points
|
||||
[(0, 1), (2, 3)]
|
||||
>>> r = Record(tags.XY, []) # not allowed
|
||||
>>> r.points
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
DataSizeError: 4099
|
||||
>>> r = Record(tags.XY, [1, 2, 3]) # odd number of coordinates
|
||||
>>> r.points
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
DataSizeError: 4099
|
||||
"""
|
||||
data_size = len(self.data)
|
||||
if not data_size or (data_size % 2):
|
||||
raise exceptions.DataSizeError(self.tag)
|
||||
return [(self.data[i], self.data[i+1]) for i in range(0, data_size, 2)]
|
||||
|
||||
@property
|
||||
def times(self):
|
||||
"""
|
||||
Convert data to tuple ``(modification time, access time)``.
|
||||
Useful for :const:`BGNLIB` and :const:`BGNSTR`.
|
||||
|
||||
>>> r = Record(tags.BGNLIB, [100, 1, 1, 1, 2, 3, 110, 8, 14, 21, 10, 35])
|
||||
>>> print(r.times[0].isoformat())
|
||||
2000-01-01T01:02:03
|
||||
>>> print(r.times[1].isoformat())
|
||||
2010-08-14T21:10:35
|
||||
>>> r = Record(tags.BGNLIB, [100, 1, 1, 1, 2, 3]) # wrong data length
|
||||
>>> r.times
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
DataSizeError: 258
|
||||
"""
|
||||
if len(self.data) != 12:
|
||||
raise exceptions.DataSizeError(self.tag)
|
||||
return (datetime(self.data[0]+1900, *self.data[1:6]),
|
||||
datetime(self.data[6]+1900, *self.data[7:12]))
|
||||
|
||||
@property
|
||||
def acls(self):
|
||||
"""
|
||||
Convert data to list of acls ``(GID, UID, ACCESS)``.
|
||||
Useful for :const:`LIBSECUR`.
|
||||
|
||||
>>> r = Record(tags.LIBSECUR, [1, 2, 3, 4, 5, 6])
|
||||
>>> r.acls
|
||||
[(1, 2, 3), (4, 5, 6)]
|
||||
>>> r = Record(tags.LIBSECUR, [1, 2, 3, 4]) # wrong data size
|
||||
>>> r.acls
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
DataSizeError: 15106
|
||||
"""
|
||||
if len(self.data) % 3:
|
||||
raise exceptions.DataSizeError(self.tag)
|
||||
return list(zip(self.data[::3], self.data[1::3], self.data[2::3]))
|
||||
|
||||
@classmethod
|
||||
def iterate(cls, stream):
|
||||
"""
|
||||
Generator function for iterating over all records in a GDSII file.
|
||||
Yields :class:`Record` objects.
|
||||
|
||||
:param stream: GDS file opened for reading in binary mode
|
||||
"""
|
||||
last = False
|
||||
while not last:
|
||||
rec = cls.read(stream)
|
||||
if rec.tag == tags.ENDLIB:
|
||||
last = True
|
||||
yield rec
|
||||
|
||||
class Reader(object):
|
||||
"""Class for buffered reading of Records"""
|
||||
__slots__ = ('current', 'stream')
|
||||
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
|
||||
def read_next(self):
|
||||
"""Read and return next record from stream."""
|
||||
self.current = Record.read(self.stream)
|
||||
return self.current
|
||||
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
doctest.testmod(optionflags=doctest.IGNORE_EXCEPTION_DETAIL)
|
||||
84
gdsii/structure.py
Normal file
84
gdsii/structure.py
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2010 Eugeniy Meshcheryakov <eugen@debian.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
:mod:`gdsii.structure` --- interface to a GDSII structure
|
||||
=========================================================
|
||||
|
||||
This module contains class that represents a GDSII structure.
|
||||
|
||||
.. moduleauthor:: Eugeniy Meshcheryakov <eugen@debian.org>
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from . import elements, record, tags, _records
|
||||
from datetime import datetime
|
||||
|
||||
_STRNAME = _records.StringRecord('name', tags.STRNAME)
|
||||
_BGNSTR = _records.TimestampsRecord('mod_time', 'acc_time', tags.BGNSTR)
|
||||
_STRCLASS = _records.SimpleOptionalRecord('strclass', tags.STRCLASS)
|
||||
|
||||
class Structure(list):
|
||||
"""
|
||||
GDSII structure class. This class is derived for :class:`list` and can
|
||||
contain one or more elements from :mod:`gdsii.elements`.
|
||||
|
||||
GDS syntax for the structure:
|
||||
.. productionlist::
|
||||
structure: BGNSTR
|
||||
: STRNAME
|
||||
: [STRCLASS]
|
||||
: {`element`}*
|
||||
: ENDSTR
|
||||
"""
|
||||
_gds_objs = (_BGNSTR, _STRNAME, _STRCLASS)
|
||||
|
||||
def __init__(self, name, mod_time=None, acc_time=None):
|
||||
"""
|
||||
Initialize the structure.
|
||||
`mod_time` and `acc_time` are set to current UTC time by default.
|
||||
"""
|
||||
list.__init__(self)
|
||||
self.name = name
|
||||
self.mod_time = mod_time if mod_time is not None else datetime.utcnow()
|
||||
self.acc_time = acc_time if acc_time is not None else datetime.utcnow()
|
||||
|
||||
def _init_optional(self):
|
||||
"""Initialize optional attributes to None."""
|
||||
self.strclass = None
|
||||
|
||||
@classmethod
|
||||
def _load(cls, gen):
|
||||
self = cls.__new__(cls)
|
||||
list.__init__(self)
|
||||
self._init_optional()
|
||||
|
||||
for obj in self._gds_objs:
|
||||
obj.read(self, gen)
|
||||
|
||||
# read elements till ENDSTR
|
||||
while gen.current.tag != tags.ENDSTR:
|
||||
self.append(elements._Base._load(gen))
|
||||
return self
|
||||
|
||||
def _save(self, stream):
|
||||
for obj in self._gds_objs:
|
||||
obj.save(self, stream)
|
||||
for elem in self:
|
||||
elem._save(stream)
|
||||
record.Record(tags.ENDSTR).save(stream)
|
||||
|
||||
def __repr__(self):
|
||||
return '<Structure: %s>' % self.name.decode()
|
||||
128
gdsii/tags.py
Normal file
128
gdsii/tags.py
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2010 Eugeniy Meshcheryakov <eugen@debian.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
:mod:`gdsii.tags` --- definitions of GDSII tags
|
||||
===============================================
|
||||
|
||||
This module containing definitions of GDSII record tags.
|
||||
|
||||
.. moduleauthor:: Eugeniy Meshcheryakov <eugen@debian.org>
|
||||
"""
|
||||
|
||||
DICT = {
|
||||
'HEADER': 0x0002,
|
||||
'BGNLIB': 0x0102,
|
||||
'LIBNAME': 0x0206,
|
||||
'UNITS': 0x0305,
|
||||
'ENDLIB': 0x0400,
|
||||
'BGNSTR': 0x0502,
|
||||
'STRNAME': 0x0606,
|
||||
'ENDSTR': 0x0700,
|
||||
'BOUNDARY': 0x0800,
|
||||
'PATH': 0x0900,
|
||||
'SREF': 0x0A00,
|
||||
'AREF': 0x0B00,
|
||||
'TEXT': 0x0C00,
|
||||
'LAYER': 0x0D02,
|
||||
'DATATYPE': 0x0E02,
|
||||
'WIDTH': 0x0F03,
|
||||
'XY': 0x1003,
|
||||
'ENDEL': 0x1100,
|
||||
'SNAME': 0x1206,
|
||||
'COLROW': 0x1302,
|
||||
'TEXTNODE': 0x1400,
|
||||
'NODE': 0x1500,
|
||||
'TEXTTYPE': 0x1602,
|
||||
'PRESENTATION': 0x1701,
|
||||
# SPACING: 0x18??
|
||||
'STRING': 0x1906,
|
||||
'STRANS': 0x1A01,
|
||||
'MAG': 0x1B05,
|
||||
'ANGLE': 0x1C05,
|
||||
# UINTEGER: 0x1D??
|
||||
# USTRING: 0x1E??
|
||||
'REFLIBS': 0x1F06,
|
||||
'FONTS': 0x2006,
|
||||
'PATHTYPE': 0x2102,
|
||||
'GENERATIONS': 0x2202,
|
||||
'ATTRTABLE': 0x2306,
|
||||
'STYPTABLE': 0x2406,
|
||||
'STRTYPE': 0x2502,
|
||||
'ELFLAGS': 0x2601,
|
||||
'ELKEY': 0x2703,
|
||||
# LINKTYPE: 0x28??
|
||||
# LINKKEYS: 0x29??
|
||||
'NODETYPE': 0x2A02,
|
||||
'PROPATTR': 0x2B02,
|
||||
'PROPVALUE': 0x2C06,
|
||||
'BOX': 0x2D00,
|
||||
'BOXTYPE': 0x2E02,
|
||||
'PLEX': 0x2F03,
|
||||
'BGNEXTN': 0x3003,
|
||||
'ENDEXTN': 0x3103,
|
||||
'TAPENUM': 0x3202,
|
||||
'TAPECODE': 0x3302,
|
||||
'STRCLASS': 0x3401,
|
||||
# RESERVED: 0x3503
|
||||
'FORMAT': 0x3602,
|
||||
'MASK': 0x3706,
|
||||
'ENDMASKS': 0x3800,
|
||||
'LIBDIRSIZE': 0x3902,
|
||||
'SRFNAME': 0x3A06,
|
||||
'LIBSECUR': 0x3B02,
|
||||
# Types used only with Custom Plus
|
||||
'BORDER': 0x3C00,
|
||||
'SOFTFENCE': 0x3D00,
|
||||
'HARDFENCE': 0x3E00,
|
||||
'SOFTWIRE': 0x3F00,
|
||||
'HARDWIRE': 0x4000,
|
||||
'PATHPORT': 0x4100,
|
||||
'NODEPORT': 0x4200,
|
||||
'USERCONSTRAINT': 0x4300,
|
||||
'SPACERERROR': 0x4400,
|
||||
'CONTACT': 0x4500
|
||||
}
|
||||
|
||||
REV_DICT = {}
|
||||
|
||||
for (key, value) in DICT.items():
|
||||
globals()[key] = value
|
||||
REV_DICT[value] = key
|
||||
|
||||
del key, value
|
||||
|
||||
def type_of_tag(tag):
|
||||
"""
|
||||
Returns type of a tag.
|
||||
|
||||
:param tag: tag ID
|
||||
:type tag: int
|
||||
:rtype: int
|
||||
|
||||
Examples:
|
||||
|
||||
>>> type_of_tag(HEADER)
|
||||
2
|
||||
>>> type_of_tag(MASK)
|
||||
6
|
||||
|
||||
"""
|
||||
return tag & 0xff
|
||||
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
40
gdsii/types.py
Normal file
40
gdsii/types.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2010 Eugeniy Meshcheryakov <eugen@debian.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
:mod:`gdsii.types` --- definitions of GDSII data types
|
||||
======================================================
|
||||
|
||||
This module contains definitions of GDSII data types.
|
||||
"""
|
||||
|
||||
DICT = {
|
||||
'NODATA': 0,
|
||||
'BITARRAY': 1,
|
||||
'INT2': 2,
|
||||
'INT4': 3,
|
||||
'REAL4': 4, # not used
|
||||
'REAL8': 5,
|
||||
'ASCII': 6
|
||||
}
|
||||
|
||||
REV_DICT = {}
|
||||
|
||||
for (key, value) in DICT.items():
|
||||
globals()[key] = value
|
||||
REV_DICT[value] = key
|
||||
|
||||
del key, value
|
||||
Loading…
Add table
Add a link
Reference in a new issue