initial commit (work in progress)
python version of parser works, BUT does not support AREF and SREF at the moment
This commit is contained in:
commit
5d939157ed
1
gds/.gitignore
vendored
Normal file
1
gds/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
__pycache__
|
7
gds/__init__.py
Normal file
7
gds/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
from .library import Library
|
||||
from .structure import Structure
|
||||
from .elements import *
|
||||
|
||||
from .record import *
|
||||
from .reader import Reader
|
||||
from .parser import *
|
76
gds/elements.py
Normal file
76
gds/elements.py
Normal file
@ -0,0 +1,76 @@
|
||||
from enum import Enum
|
||||
|
||||
class Transformation(object):
|
||||
mirror_x = 0
|
||||
|
||||
absolute_rotation = 0
|
||||
absolute_magnification = 0
|
||||
|
||||
zoom = 1
|
||||
rotation = 0
|
||||
|
||||
class Element(object):
|
||||
elflags = 0
|
||||
plex = 0
|
||||
datatype = 0
|
||||
|
||||
class Drawable(object):
|
||||
layer = 0
|
||||
|
||||
class Boundary(Element, Drawable):
|
||||
points = []
|
||||
|
||||
class Path(Element, Drawable):
|
||||
class Styles(Enum):
|
||||
SQUARE_ENDS = 0
|
||||
ROUNDED_ENDS = 1
|
||||
OFFSET_ENDS = 2
|
||||
CUSTOM_END = 4
|
||||
|
||||
extendEnd = [0,0] # extend past start and end
|
||||
width = 0
|
||||
pathStyle = Styles.SQUARE_ENDS
|
||||
|
||||
points = []
|
||||
|
||||
class Text(Element, Drawable):
|
||||
class VJust(Enum):
|
||||
Top = 0
|
||||
Middle = 1
|
||||
Bottom = 2
|
||||
|
||||
class HJust(Enum):
|
||||
Left = 0
|
||||
Center = 1
|
||||
Right = 2
|
||||
|
||||
# text info
|
||||
string = ""
|
||||
position = (0,0)
|
||||
|
||||
# presentation
|
||||
fontnumber = 0
|
||||
verticalJustification = VJust.Top
|
||||
horizontalJustification = HJust.Left
|
||||
|
||||
# optional path info
|
||||
pathStype = Path.Styles.SQUARE_ENDS
|
||||
pathWidth = 0
|
||||
|
||||
transformation = Transformation()
|
||||
|
||||
|
||||
class Box(Element, Drawable):
|
||||
points = []
|
||||
|
||||
class SRef(Element):
|
||||
Position = None
|
||||
Structure = ""
|
||||
|
||||
transformation = Transformation()
|
||||
|
||||
# tree
|
||||
Parent = None
|
||||
Children = []
|
||||
|
||||
|
14
gds/library.py
Normal file
14
gds/library.py
Normal file
@ -0,0 +1,14 @@
|
||||
from datetime import datetime
|
||||
|
||||
class Library(object):
|
||||
version = 0
|
||||
name = "NONAME"
|
||||
|
||||
last_access = datetime.now()
|
||||
last_mod = datetime.now()
|
||||
|
||||
# unit setup
|
||||
units_per_dbunit = 1
|
||||
meters_per_unit = 1
|
||||
|
||||
structures = {}
|
352
gds/parser.py
Normal file
352
gds/parser.py
Normal file
@ -0,0 +1,352 @@
|
||||
from . import *
|
||||
from enum import Enum
|
||||
|
||||
class errors(Enum):
|
||||
UNEXPECTED_EOF = "unexpected EOF"
|
||||
EXPECTED_HEADER = "expected file header"
|
||||
EXPECTED_BGNLIB = "expected beginning of library"
|
||||
EXPECTED_LIBNAME = "Library name is missing"
|
||||
EXPECTED_ENDMASK = "MASK records was not terminated by ENDMASK"
|
||||
EXPECTED_STRNAME = "encountered nameless structure"
|
||||
EXPECTED_UNITS = "no unit record in file"
|
||||
EXPECTED_LAYER = "expected a layer record in element"
|
||||
EXPECTED_POINTS = "expected coordinates for element"
|
||||
EXPECTED_POINT = "expected coordinate for element"
|
||||
EXPECTED_DATATYPE = "expected datatype"
|
||||
EXPECTED_ENDEL = "end of element is missing"
|
||||
EXPECTED_TEXTTYPE = "expected TEXTTYPE"
|
||||
EXPECTED_STRING = "textelement is missing text"
|
||||
EXPECTED_BOXTYPE = "expected BOXTYPE"
|
||||
INVALID_PATHTYPE = "pathtype must be in range [0,2]"
|
||||
|
||||
class Warnings(Enum):
|
||||
EXPECTED_ENDLIB = "missing end of library"
|
||||
|
||||
class ParserError(Exception):
|
||||
pass
|
||||
|
||||
class Parser(object):
|
||||
# parser stack
|
||||
_token = None
|
||||
last_token = None
|
||||
|
||||
# parser state
|
||||
Library = Library()
|
||||
current_structure = None
|
||||
|
||||
def __init__(self, reader):
|
||||
self.reader = reader
|
||||
|
||||
@property
|
||||
def token(self):
|
||||
return self._token
|
||||
|
||||
@token.setter
|
||||
def token(self, t):
|
||||
self.last_token = self._token
|
||||
self._token = t
|
||||
|
||||
def next_token(self, throw=False):
|
||||
self.token = self.reader.read_record()
|
||||
|
||||
if not self.token and throw:
|
||||
raise ParserError(errors.UNEXPECTED_EOF)
|
||||
|
||||
# if self.token:
|
||||
# print(self.token.ident.name)
|
||||
|
||||
return self.token
|
||||
|
||||
def parse_lib(self):
|
||||
# read header
|
||||
if not self.next_token() or self.token.ident != Records.HEADER:
|
||||
raise ParserError(errors.EXPECTED_HEADER)
|
||||
|
||||
self.Library.version = self.reader.read_short()
|
||||
|
||||
# must see BGNLIB
|
||||
if not self.next_token() or self.token.ident != Records.BGNLIB:
|
||||
raise ParserError(errors.EXPECTED_BGNLIB)
|
||||
|
||||
self.Library.last_mod = self.reader.read_date()
|
||||
self.Library.last_access = self.reader.read_date()
|
||||
|
||||
if not self.next_token() or self.token.ident != Records.LIBNAME:
|
||||
raise ParserError(errors.EXPECTED_LIBNAME)
|
||||
|
||||
self.Library.name = self.reader.read_ascii(self.token.len)
|
||||
|
||||
# read optional records
|
||||
while self.next_token():
|
||||
if self.token.ident == Records.REFLIBS or \
|
||||
self.token.ident == Records.FONTS or \
|
||||
self.token.ident == Records.ATTRTABLE or \
|
||||
self.token.ident == Records.GENERATIONS:
|
||||
# skip these records
|
||||
self.reader.skip(self.token.len)
|
||||
|
||||
elif self.token.ident == Records.FORMAT:
|
||||
self.reader.skip(self.token.len)
|
||||
|
||||
# look for optional mask
|
||||
while self.next_token().ident == Records.MASK:
|
||||
self.reader.skip(self.token.len)
|
||||
|
||||
if self.token.ident != Records.ENDMASK:
|
||||
raise ParserError(errors.EXPECTED_ENDMASK)
|
||||
|
||||
else:
|
||||
# continue
|
||||
break
|
||||
|
||||
if self.token.ident != Records.UNITS:
|
||||
raise ParserError(errors.EXPECTED_UNITS)
|
||||
|
||||
# read units
|
||||
self.Library.units_per_dbunit = self.reader.read_double()
|
||||
self.Library.meters_per_unit = self.reader.read_double()
|
||||
|
||||
while self.next_token() and self.parse_structure():
|
||||
pass
|
||||
|
||||
if self.token.ident != Records.ENDLIB:
|
||||
print(Warnings.EXPECTED_ENDLIB.value)
|
||||
|
||||
def parse_structure(self):
|
||||
self.structure = Structure()
|
||||
|
||||
if self.token.ident != Records.BGNSTR:
|
||||
return False
|
||||
|
||||
self.structure.last_mod = self.reader.read_date()
|
||||
self.structure.last_access = self.reader.read_date()
|
||||
|
||||
# read sname
|
||||
if not self.next_token() or self.token.ident != Records.STRNAME:
|
||||
raise ParserError(errors.EXPECTED_STRNAME)
|
||||
|
||||
self.structure.name = self.reader.read_ascii(self.token.len)
|
||||
|
||||
while self.next_token() and self.token.ident != Records.ENDSTR:
|
||||
if self.token.ident == Records.BOUNDARY:
|
||||
self.parse_boundary()
|
||||
elif self.token.ident == Records.PATH:
|
||||
self.parse_path()
|
||||
elif self.token.ident == Records.TEXT:
|
||||
self.parse_text()
|
||||
elif self.token.ident == Records.SREF:
|
||||
self.parse_sref()
|
||||
else:
|
||||
self.reader.skip(self.token.len)
|
||||
|
||||
self.Library.structures[self.structure.name] = self.structure
|
||||
self.structure = None
|
||||
|
||||
return True
|
||||
|
||||
def parse_element(self, element):
|
||||
if self.token.ident == Records.ELFLAGS:
|
||||
element.elflags = self.reader.read_short()
|
||||
self.next_token(True)
|
||||
|
||||
if self.token.ident == Records.PLEX:
|
||||
element.plex = self.read_int()
|
||||
self.next_token(True)
|
||||
|
||||
|
||||
def parse_boundary(self):
|
||||
element = Boundary()
|
||||
if not self.next_token():
|
||||
raise ParserError(errors.EXPECTED_LAYER)
|
||||
|
||||
self.parse_element(element)
|
||||
|
||||
if self.token.ident == Records.LAYER:
|
||||
element.layer = self.reader.read_short()
|
||||
|
||||
if not self.next_token() or self.token.ident != Records.DATATYPE:
|
||||
raise ParserError(errors.EXPECTED_DATATYPE)
|
||||
|
||||
element.datatype = self.reader.read_short()
|
||||
|
||||
if not self.next_token() or self.token.ident != Records.XY:
|
||||
raise ParserError(errors.EXPECTED_POINTS)
|
||||
|
||||
element.points = self.reader.read_coords(self.token.len)
|
||||
|
||||
if not self.next_token() or self.token.ident != Records.ENDEL:
|
||||
raise ParserError(errors.EXPECTED_ENDEL)
|
||||
|
||||
self.structure.elements.append(element)
|
||||
|
||||
def parse_path(self):
|
||||
element = Path()
|
||||
if not self.next_token():
|
||||
raise ParserError(errors.EXPECTED_LAYER)
|
||||
|
||||
self.parse_element(element)
|
||||
|
||||
if self.token.ident != Records.LAYER:
|
||||
raise ParserError(errors.EXPECTED_LAYER)
|
||||
|
||||
element.layer = self.reader.read_short()
|
||||
|
||||
if not self.next_token() or self.token.ident != Records.DATATYPE:
|
||||
raise ParserError(errors.EXPECTED_DATATYPE)
|
||||
|
||||
element.datatype = self.reader.read_short()
|
||||
|
||||
self.next_token(True)
|
||||
|
||||
if self.token.ident == Records.PATHTYPE:
|
||||
pathtype = self.reader.read_short()
|
||||
if pathtype < 0 or pathtype > 4 or pathtype == 3:
|
||||
raise ParserError(errors.INVALID_PATHTYPE)
|
||||
|
||||
element.pathStyle = Path.Styles(pathtype)
|
||||
self.next_token(True)
|
||||
|
||||
if self.token.ident == Records.WIDTH:
|
||||
element.width = self.reader.read_int()
|
||||
self.next_token(True)
|
||||
|
||||
if self.token.ident == Records.BGNEXTN:
|
||||
element.extendEnd[0] = self.reader.read_int()
|
||||
self.next_token(True)
|
||||
elif element.pathStyle == Path.Styles.OFFSET_ENDS:
|
||||
element.extendEnd[0] = element.width/2
|
||||
|
||||
if self.token.ident == Records.ENDEXTN:
|
||||
element.extendEnd[1] = self.reader.read_int()
|
||||
self.next_token(True)
|
||||
elif element.pathStyle == Path.Styles.OFFSET_ENDS:
|
||||
element.extendEnd[1] = element.width/2
|
||||
|
||||
if self.token.ident != Records.XY:
|
||||
raise ParserError(errors.EXPECTED_POINTS)
|
||||
|
||||
element.points = self.reader.read_coords(self.token.len)
|
||||
|
||||
if not self.next_token() or self.token.ident != Records.ENDEL:
|
||||
raise ParserError(errors.EXPECTED_ENDEL)
|
||||
|
||||
self.structure.elements.append(element)
|
||||
|
||||
def parse_text(self):
|
||||
element = Text()
|
||||
if not self.next_token():
|
||||
raise ParserError(errors.EXPECTED_LAYER)
|
||||
|
||||
self.parse_element(element)
|
||||
|
||||
if not self.token.ident == Records.LAYER:
|
||||
raise ParserError(errors.EXPECTED_LAYER)
|
||||
|
||||
element.layer = self.reader.read_short()
|
||||
|
||||
if not self.next_token() or self.token.ident != Records.TEXTTYPE:
|
||||
raise ParserError(errors.EXPECTED_TEXTTYPE)
|
||||
|
||||
element.datatype = self.reader.read_short()
|
||||
|
||||
self.next_token(True)
|
||||
|
||||
if self.token.ident == Records.PRESENTATION:
|
||||
temp = self.reader.read_short()
|
||||
element.fontnumber = (temp>>10)&0x03
|
||||
element.verticalJustification = Text.VJust((temp>>12)&0x03)
|
||||
element.horizontalJustification = Text.HJust((temp>>14)&0X03)
|
||||
|
||||
self.next_token(True)
|
||||
|
||||
if self.token.ident == Records.PATHTYPE:
|
||||
element.pathStype = Path.Styles(self.reader.read_short())
|
||||
self.next_token(True)
|
||||
|
||||
if self.token.ident == Records.WIDTH:
|
||||
element.pathWidth = self.reader.read_short()
|
||||
self.next_token(True)
|
||||
|
||||
self.parse_strans(element.transformation)
|
||||
|
||||
if self.token.ident != Records.XY:
|
||||
raise ParserError(errors.EXPECTED_POINT)
|
||||
|
||||
element.point = self.reader.read_coord()
|
||||
|
||||
# skip potential array (if given)
|
||||
self.reader.skip(self.token.len - 8)
|
||||
|
||||
if not self.next_token() or self.token.ident != Records.STRING:
|
||||
raise ParserError(errors.EXPECTED_STRING)
|
||||
|
||||
element.string = self.reader.read_ascii(self.token.len)
|
||||
|
||||
if not self.next_token() or self.token.ident != Records.ENDEL:
|
||||
raise ParserError(errors.EXPECTED_ENDEL)
|
||||
|
||||
self.structure.elements.append(element)
|
||||
|
||||
def parse_strans(self, trans:Transformation):
|
||||
if self.token.ident != Records.STRANS:
|
||||
return
|
||||
|
||||
flags = self.reader.read_short()
|
||||
if flags & 0x01:
|
||||
trans.mirror_x = True
|
||||
|
||||
if flags & 0x2000:
|
||||
trans.absolute_magnification = True
|
||||
|
||||
if flags & 0x4000:
|
||||
trans.absolute_rotation = True
|
||||
|
||||
self.next_token(True)
|
||||
|
||||
if self.token.ident == Records.MAG:
|
||||
trans.zoom = self.reader.read_double()
|
||||
self.next_token(True)
|
||||
|
||||
if self.token.ident == Records.ANGLE:
|
||||
trans.rotation = self.reader.read_double()
|
||||
self.next_token(True)
|
||||
|
||||
def parse_box(self):
|
||||
element = Box()
|
||||
if not self.next_token():
|
||||
raise ParserError(errors.EXPECTED_LAYER)
|
||||
|
||||
self.parse_element(element)
|
||||
|
||||
if self.token.ident != Records.LAYER:
|
||||
raise ParserError(errors.EXPECTED_LAYER)
|
||||
|
||||
element.layer = self.reader.read_short()
|
||||
|
||||
if not self.next_token() or self.token.ident != Records.BOXTYPE:
|
||||
raise ParserError(errors.EXPECTED_BOXTYPE)
|
||||
|
||||
element.datatype = self.reader.read_short()
|
||||
|
||||
if not self.next_token() or self.token.ident != Records.XY:
|
||||
raise ParserError(errors.EXPECTED_POINTS)
|
||||
|
||||
element.points = self.reader.read_coords(self.token.len)
|
||||
|
||||
if not self.next_token() or self.token.ident != Records.ENDEL:
|
||||
raise ParserError(errors.EXPECTED_ENDEL)
|
||||
|
||||
self.structure.elements.append(element)
|
||||
|
||||
def parse_sref(self):
|
||||
while self.next_token() and self.token.ident != Records.ENDEL:
|
||||
self.reader.skip(self.token.len)
|
||||
|
||||
def parse_file(file):
|
||||
r = Reader(file)
|
||||
parser = Parser(r)
|
||||
parser.parse_lib()
|
||||
|
||||
return parser.Library
|
||||
|
||||
|
97
gds/reader.py
Normal file
97
gds/reader.py
Normal file
@ -0,0 +1,97 @@
|
||||
from datetime import datetime
|
||||
from .record import *
|
||||
|
||||
class Reader(object):
|
||||
def __init__(self, file):
|
||||
self.stream = file
|
||||
|
||||
def skip(self, n):
|
||||
self.stream.read(n)
|
||||
|
||||
def read_int(self):
|
||||
temp = self.stream.read(4)
|
||||
if len(temp) != 4:
|
||||
return None
|
||||
|
||||
return int(temp[3]) | int(temp[2]) << 8 | int(temp[1]) << 16 | int(temp[0]) << 24
|
||||
|
||||
def read_short(self):
|
||||
temp = self.stream.read(2)
|
||||
if len(temp) != 2:
|
||||
return None
|
||||
|
||||
return int(temp[1]) | int(temp[0]) << 8
|
||||
|
||||
def read_double(self):
|
||||
temp = self.stream.read(8)
|
||||
if len(temp) != 8:
|
||||
return None
|
||||
|
||||
result = 0
|
||||
|
||||
for i in temp:
|
||||
if int(i) != 0:
|
||||
# read double
|
||||
result = 1
|
||||
|
||||
for j in range(1,8):
|
||||
result += float(temp[8-j])/(2**(j*8))
|
||||
|
||||
exp = int(temp[0]) & 0x7F
|
||||
exp -= 64
|
||||
result *= 16**exp
|
||||
|
||||
if int(temp[0]) & 0x80:
|
||||
result += -1
|
||||
|
||||
return result
|
||||
|
||||
# double is Zero
|
||||
return 0
|
||||
|
||||
def read_ascii(self, len):
|
||||
return self.stream.read(len).decode("ASCII").strip()
|
||||
|
||||
def read_record(self):
|
||||
result = Record()
|
||||
try:
|
||||
result.len = self.read_short()
|
||||
result.ident = Records(self.read_short())
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
result.len -= 4 # remove record header len
|
||||
|
||||
return result
|
||||
|
||||
def read_date(self):
|
||||
# date
|
||||
year = self.read_short()
|
||||
month = self.read_short()
|
||||
day = self.read_short()
|
||||
|
||||
# time
|
||||
hour = self.read_short()
|
||||
minute = self.read_short()
|
||||
second = self.read_short()
|
||||
|
||||
return datetime(year=year, month=month, day=day, hour=hour, minute=minute, second=second)
|
||||
|
||||
def read_coord(self):
|
||||
X = self.read_int()
|
||||
Y = self.read_int()
|
||||
|
||||
return (X,Y)
|
||||
|
||||
def read_coords(self, len):
|
||||
len /= 8
|
||||
result = []
|
||||
while len > 0:
|
||||
point = self.read_coord()
|
||||
if not point:
|
||||
return None
|
||||
|
||||
result.append(point)
|
||||
len -= 1
|
||||
|
||||
return result
|
52
gds/record.py
Normal file
52
gds/record.py
Normal file
@ -0,0 +1,52 @@
|
||||
from enum import Enum
|
||||
|
||||
class Records(Enum):
|
||||
UNKNOWN = 0x0000
|
||||
HEADER = 0x0002
|
||||
BGNLIB = 0x0102
|
||||
LIBNAME = 0x0206
|
||||
REFLIBS = 0x1F06
|
||||
FONTS = 0x2006
|
||||
ATTRTABLE = 0x2306
|
||||
GENERATIONS = 0x2202
|
||||
FORMAT = 0x3602
|
||||
MASK = 0x3706
|
||||
ENDMASKS = 0x3800
|
||||
UNITS = 0x0305
|
||||
ENDLIB = 0x0400
|
||||
BGNSTR = 0x0502
|
||||
STRNAME = 0x0606
|
||||
ENDEL = 0x1100
|
||||
ENDSTR = 0x0700
|
||||
BOUNDARY = 0x0800
|
||||
PATH = 0x0900
|
||||
SREF = 0x0A00
|
||||
AREF = 0x0B00
|
||||
TEXT = 0x0C00
|
||||
NODE = 0x1500
|
||||
BOX = 0x2D00
|
||||
ELFLAGS = 0x2601
|
||||
PLEX = 0x2F03
|
||||
LAYER = 0x0D02
|
||||
DATATYPE = 0x0E02
|
||||
XY = 0x1003
|
||||
PATHTYPE = 0x2102
|
||||
WIDTH = 0x0F03
|
||||
BGNEXTN = 0x3003
|
||||
ENDEXTN = 0x3103
|
||||
SNAME = 0x1206
|
||||
STRANS = 0x1A01
|
||||
MAG = 0x1B05
|
||||
ANGLE = 0x1C05
|
||||
COLROW = 0x1302
|
||||
TEXTTYPE = 0x1602
|
||||
PRESENTATION = 0x1701
|
||||
NODETYPE = 0x2A02
|
||||
BOXTYPE = 0x2E02
|
||||
STRING = 0x1906
|
||||
PROPATTR = 0x2B02
|
||||
PROPVALUE = 0x2C06
|
||||
|
||||
class Record(object):
|
||||
ident = Records.UNKNOWN
|
||||
len = 0
|
10
gds/structure.py
Normal file
10
gds/structure.py
Normal file
@ -0,0 +1,10 @@
|
||||
from datetime import datetime
|
||||
|
||||
class Structure(object):
|
||||
# metainfo
|
||||
creation_date = datetime.now()
|
||||
last_mod = datetime.now()
|
||||
name = "NONAME"
|
||||
|
||||
# contains all the elements
|
||||
elements = []
|
20
test.py
Normal file
20
test.py
Normal file
@ -0,0 +1,20 @@
|
||||
import gds
|
||||
import sys
|
||||
|
||||
for arg in sys.argv[1:]:
|
||||
f = open(arg, "rb")
|
||||
try:
|
||||
lib = gds.parse_file(f)
|
||||
print("file version: {}".format(lib.version))
|
||||
print("last access: {}".format(lib.last_access.isoformat()))
|
||||
print("last modification: {}".format(lib.last_mod.isoformat()))
|
||||
print("m/unit : {}".format(lib.meters_per_unit))
|
||||
print("unit/dbunit : {}".format(lib.units_per_dbunit))
|
||||
|
||||
print("library name : {}".format(lib.name))
|
||||
print("contains a total of {} structure(s)".format(len(lib.structures)))
|
||||
|
||||
except gds.ParserError as e:
|
||||
print("parser error: {}".format(e))
|
||||
finally:
|
||||
f.close()
|
Loading…
Reference in New Issue
Block a user