#!/usr/bin/env python """ Parse the job layout specification file. Requires: - SimpleParse 2.1 or higher http://simpleparse.sourceforge.net -------------------------------------------------------------------- This program is licensed under the GNU General Public License (GPL) Version 3. See http://www.fsf.org for details of the license. Rugged Circuits LLC http://ruggedcircuits.com/gerbmerge """ import sys import string from simpleparse.parser import Parser import config import jobs declaration = r''' file := (commentline/nullline/rowspec)+ rowspec := ts, 'Row', ws, '{'!, ts, comment?, '\n', rowjob+, ts, '}'!, ts, comment?, '\n' rowjob := jobspec/colspec/commentline/nullline colspec := ts, 'Col', ws, '{'!, ts, comment?, '\n', coljob+, ts, '}'!, ts, comment?, '\n' coljob := jobspec/rowspec/commentline/nullline jobspec := ts, (paneljobspec/basicjobspec), ts, comment?, '\n' basicjobspec := id, (rotation)? paneljobspec := 'Not yet implemented' #paneljobspec := int, [xX], int, ws, basicjobspec comment := ([#;]/'//'), -'\n'* commentline := ts, comment, '\n' nullline := ts, '\n' rotation := ws, 'Rotate', ('90'/'180'/'270')? ws := [ \t]+ ts := [ \t]* id := [a-zA-Z0-9], [a-zA-Z0-9_-]* int := [0-9]+ ''' class Panel: # Meant to be subclassed as either a Row() or Col() def __init__(self): self.x = None self.y = None self.jobs = [] # List (left-to-right or bottom-to-top) of JobLayout() or Row()/Col() objects def canonicalize(self): # Return plain list of JobLayout objects at the roots of all trees L = [] for job in self.jobs: L = L + job.canonicalize() return L def addjob(self, job): # Either a JobLayout class or Panel (sub)class assert isinstance(job, Panel) or isinstance(job, jobs.JobLayout) self.jobs.append(job) def addwidths(self): "Return width in inches" width = 0.0 for job in self.jobs: width += job.width_in() + config.Config['xspacing'] width -= config.Config['xspacing'] return width def maxwidths(self): "Return maximum width in inches of any one subpanel" width = 0.0 for job in self.jobs: width = max(width,job.width_in()) return width def addheights(self): "Return height in inches" height = 0.0 for job in self.jobs: height += job.height_in() + config.Config['yspacing'] height -= config.Config['yspacing'] return height def maxheights(self): "Return maximum height in inches of any one subpanel" height = 0.0 for job in self.jobs: height = max(height,job.height_in()) return height def writeGerber(self, fid, layername): for job in self.jobs: job.writeGerber(fid, layername) def writeExcellon(self, fid, tool): for job in self.jobs: job.writeExcellon(fid, tool) def writeDrillHits(self, fid, tool, toolNum): for job in self.jobs: job.writeDrillHits(fid, tool, toolNum) def writeCutLines(self, fid, drawing_code, X1, Y1, X2, Y2): for job in self.jobs: job.writeCutLines(fid, drawing_code, X1, Y1, X2, Y2) def drillhits(self, tool): hits = 0 for job in self.jobs: hits += job.drillhits(tool) return hits def jobarea(self): area = 0.0 for job in self.jobs: area += job.jobarea() return area class Row(Panel): def __init__(self): Panel.__init__(self) self.LR = 1 # Horizontal arrangement def width_in(self): return self.addwidths() def height_in(self): return self.maxheights() def setPosition(self, x, y): # In inches self.x = x self.y = y for job in self.jobs: job.setPosition(x,y) x += job.width_in() + config.Config['xspacing'] class Col(Panel): def __init__(self): Panel.__init__(self) self.LR = 0 # Vertical arrangement def width_in(self): return self.maxwidths() def height_in(self): return self.addheights() def setPosition(self, x, y): # In inches self.x = x self.y = y for job in self.jobs: job.setPosition(x,y) y += job.height_in() + config.Config['yspacing'] def canonicalizePanel(panel): L = [] for job in panel: L = L + job.canonicalize() return L def findJob(jobname, rotated, Jobs=config.Jobs): """ Find a job in config.Jobs, possibly rotating it If job not in config.Jobs add it for future reference Return found job """ if rotated == 90: fullname = jobname + '*rotated90' elif rotated == 180: fullname = jobname + '*rotated180' elif rotated == 270: fullname = jobname + '*rotated270' else: fullname = jobname try: for existingjob in Jobs.keys(): if existingjob.lower() == fullname.lower(): ## job names are case insensitive job = Jobs[existingjob] return jobs.JobLayout(job) except: pass # Perhaps we just don't have a rotated job yet if rotated: try: for existingjob in Jobs.keys(): if existingjob.lower() == jobname.lower(): ## job names are case insensitive job = Jobs[existingjob] except: raise RuntimeError, "Job name '%s' not found" % jobname else: raise RuntimeError, "Job name '%s' not found" % jobname # Make a rotated job job = jobs.rotateJob(job, rotated) Jobs[fullname] = job return jobs.JobLayout(job) def parseJobSpec(spec, data): for jobspec in spec: if jobspec[0] in ('ts','comment'): continue assert jobspec[0] in ('paneljobspec','basicjobspec') if jobspec[0] == 'basicjobspec': namefield = jobspec[3][0] jobname = data[namefield[1]:namefield[2]] if len(jobspec[3]) > 1: rotationfield = jobspec[3][1] rotation = data[ rotationfield[1] + 1: rotationfield[2] ] if (rotation == "Rotate") or (rotation == "Rotate90"): rotated = 90 elif rotation == "Rotate180": rotated = 180 elif rotation == "Rotate270": rotated = 270 else: raise RuntimeError, "Unsupported rotation: %s" % rotation else: rotated = 0 return findJob(jobname, rotated) else: raise RuntimeError, "Matrix panels not yet supported" def parseColSpec(spec, data): jobs = Col() for coljob in spec: if coljob[0] in ('ts','ws','comment'): continue assert coljob[0] == 'coljob' job = coljob[3][0] if job[0] in ('commentline','nullline'): continue assert job[0] in ('jobspec','rowspec') if job[0] == 'jobspec': jobs.addjob(parseJobSpec(job[3],data)) else: jobs.addjob(parseRowSpec(job[3],data)) return jobs def parseRowSpec(spec, data): jobs = Row() for rowjob in spec: if rowjob[0] in ('ts','ws','comment'): continue assert rowjob[0] == 'rowjob' job = rowjob[3][0] if job[0] in ('commentline','nullline'): continue assert job[0] in ('jobspec','colspec') if job[0] == 'jobspec': jobs.addjob(parseJobSpec(job[3],data)) else: jobs.addjob(parseColSpec(job[3],data)) return jobs def parseLayoutFile(fname): """config.Jobs is a dictionary of ('jobname', Job Object). The return value is a nested array. The primary dimension of the array is one row: [ Row1, Row2, Row3 ] Each row element consists of a list of jobs or columns (i.e., JobLayout or Col objects). Each column consists of a list of either jobs or rows. These are recursive, so it can look like: [ Row([JobLayout(), Col([ Row([JobLayout(), JobLayout()]), JobLayout() ]), JobLayout() ]), # That was row 0 Row([JobLayout(), JobLayout()]) # That was row 1 ] This is a panel with two rows. In the first row there is a job, a column, and another job, from left to right. In the second row there are two jobs, from left to right. The column in the first row has two jobs side by side, then another one above them. """ try: fid = file(fname, 'rt') except Exception, detail: raise RuntimeError, "Unable to open layout file: %s\n %s" % (fname, str(detail)) data = fid.read() fid.close() parser = Parser(declaration, "file") # Replace all CR's in data with nothing, to convert DOS line endings # to unix format (all LF's). data = string.replace(data, '\x0D', '') tree = parser.parse(data) # Last element of tree is number of characters parsed if not tree[0]: raise RuntimeError, "Layout file cannot be parsed" if tree[2] != len(data): raise RuntimeError, "Parse error at character %d in layout file" % tree[2] Rows = [] for rowspec in tree[1]: if rowspec[0] in ('nullline', 'commentline'): continue assert rowspec[0]=='rowspec' Rows.append(parseRowSpec(rowspec[3], data)) return Rows if __name__=="__main__": fid = file(sys.argv[1]) testdata = fid.read() fid.close() parser = Parser(declaration, "file") import pprint pprint.pprint(parser.parse(testdata))