Advertisement
LilithBryant

gEDA PCB parsing tool/thermals fixer

May 25th, 2014
218
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 19.17 KB | None | 0 0
  1. #!/bin/env python
  2.  
  3. # Copyright 2014  Lilith Bryant
  4. # Licence:  GPLv2
  5.  
  6. from __future__ import print_function
  7. import pyparsing as pp
  8.  
  9. import re
  10. import sys
  11. import copy
  12. import os
  13.  
  14. class Measurement(object):
  15.     def __init__(self,s):        
  16.         if s.endswith("nm"):
  17.             m=float(s[:-2])
  18.             u="nm"
  19.             mult=1
  20.         elif s.endswith("mm"):
  21.             m=float(s[:-2])
  22.             u="mm"
  23.             mult=1000000
  24.         elif s.endswith("mil"):
  25.             m=float(s[:-3])
  26.             u="mil"
  27.             mult=25400      
  28.         else:
  29.             m=float(s)
  30.             u=""
  31.             mult=2540000
  32.            
  33.         self.units=u
  34.         if m==int(m):
  35.             m=int(m)
  36.         self.raw=m
  37.         self.nm=int(round(self.raw*mult))
  38.  
  39.     def AsNM(self,mil):
  40.         return self.nm
  41.    
  42.     def SetNM(self,nm):
  43.         self.nm=nm
  44.         self.units="nm"
  45.         self.raw=nm
  46.        
  47.     def SetMil(self,mil):
  48.         self.nm=int(round(mil*25400))
  49.         self.units="mil"
  50.         self.raw=mil
  51.    
  52.     def __str__(self):
  53.         return str(self.raw)+self.units
  54.  
  55. class StringFlags(object):
  56.     L=pp.Literal("(").suppress()
  57.     R=pp.Literal(")").suppress()
  58.     PartStart=pp.Regex(r"[^,\(\)]*")    
  59.     CSV=pp.delimitedList(PartStart)        
  60.     Part=pp.Group( PartStart + pp.Group( pp.Optional( L + CSV + R ) ) )    
  61.     Parts=pp.delimitedList(Part)
  62.    
  63.     def __init__(self,s):        
  64.         self.s=s
  65.         All=StringFlags.Parts.parseString(s)
  66.         self.flags=set([ PP[0]  for PP in All if PP[0]!="thermal"])            
  67.         ThermalFlags=[ PP for PP in All if PP[0]=="thermal"]            
  68.         if len(ThermalFlags)==0:
  69.             self.thermals={}
  70.         else:
  71.             T=set(ThermalFlags[0][1])            
  72.             self.thermals=dict(  [  (i[0], (i+"!")[1] )  for i in T ] )
  73.        
  74.     def __contains__(self,s):
  75.         if s=="thermal":
  76.             return len(self.thermals)>0
  77.         else:
  78.             return s in self.flags
  79.        
  80.     def Add(self,s):
  81.         self.flags.add(s)
  82.  
  83.     def Remove(self,s):
  84.         if s in self.flags:
  85.             self.flags.remove(s)
  86.  
  87.     def AddThermal(self,n,type):
  88.         self.thermals[n]=type
  89.  
  90.     def RemoveThermal(self,n):
  91.         if n in self.thermals:
  92.             del self.thermals[n]
  93.        
  94.     def RemoveAllThermal(self):
  95.         self.thermals={}
  96.        
  97.     def __str__(self):
  98.         if self.thermals is not None and len(self.thermals)>0:
  99.             s=[  str(i[0])+(str(i[1]).replace("!",""))  for i in self.thermals.items() ]
  100.             t="thermal("+",".join(sorted(s))+")"
  101.             return ",".join(list(self.flags)+[t])        
  102.         else:
  103.             return ",".join(self.flags)
  104.  
  105. def BuildBNF(pp):
  106.     #All ZeroOrMore things must have a setResultsName("xxx",True) for writer to work
  107.    
  108.     S=pp.Suppress
  109.     C=pp.Combine
  110.     G=pp.Group
  111.    
  112.     LRND=S(pp.Literal("("))
  113.     RRND=S(pp.Literal(")"))
  114.     LSQ=S(pp.Literal("["))
  115.     RSQ=S(pp.Literal("]"))
  116.    
  117.     Str=pp.QuotedString('"')
  118.     Char=pp.Regex(r"'.'")
  119.    
  120.     Meas=pp.Regex(r"-?[0-9\\.]+[a-z]*")
  121.     Meas.setParseAction(lambda t: Measurement(t[0]))
  122.  
  123.     MeasArea=pp.Regex(r"-?[0-9\\.]+[a-z]*")
  124.     MeasArea.setParseAction(lambda t:float(t[0]))
  125.    
  126.     Digits=pp.Regex(r"-?[0-9]+")
  127.     Digits.setParseAction(lambda t:int(t[0]))
  128.    
  129.     Float=pp.Regex(r"-?[0-9\\.]+")
  130.     Float.setParseAction(lambda t:float(t[0]))
  131.  
  132.     Comment=pp.Literal("#") + pp.restOfLine
  133.    
  134.     SFlags=C(Str)
  135.     SFlags.setParseAction(lambda t: StringFlags(t[0]))
  136.    
  137.     QSFlags=SFlags
  138.    
  139.     FileVersion=pp.Keyword("FileVersion") + LSQ + Digits("Version")+ RSQ
  140.     PCB = pp.Keyword("PCB")+ LSQ+(Str("Name")+Meas("Width")+Meas("Height"))("PCB")+RSQ
  141.     Grid = pp.Keyword("Grid")+ LSQ+Meas("Step")+Meas("OffsetX")+Meas("OffsetY")+Digits("Visible")+RSQ
  142.     PolyArea = pp.Keyword("PolyArea")+ LSQ+Float("Area")+RSQ
  143.     Thermal = pp.Keyword("Thermal")+ LSQ+Float("Scale")+RSQ
  144.     DRC = pp.Keyword("DRC")+ LSQ+ Meas("Bloat")+ Meas("Shrink")+ Meas("Line")+ Meas("Silk")+ Meas("Drill")+ Meas("Ring")+RSQ
  145.     Flags = pp.Keyword("Flags")+ LRND+ QSFlags("Flags")+RRND
  146.     Groups = pp.Keyword("Groups")+ LRND+ Str("Groups")+RRND
  147.     Styles = pp.Keyword("Styles")+ LSQ+ Str("Styles")+RSQ
  148.     Mark = pp.Keyword("Mark")+ LSQ+ Meas("X")+Meas("Y")+RSQ
  149.     Cursor = pp.Keyword("Cursor")+ LSQ+ Meas("X")+Meas("Y")+Float("Zoom")+RSQ
  150.  
  151.     PCBAttribute = pp.Keyword("Attribute")+ LRND+ Str("AttrKey")+Str("AttrValue")+RRND
  152.     SymbolLine = pp.Keyword("SymbolLine")+ LSQ+ Meas("X1")+Meas("Y1")+Meas("X2")+Meas("Y2")+Meas("Thickness")+RSQ
  153.        
  154.     SymContent2 = SymbolLine
  155.     SymContent = G(SymContent2)
  156.     SymContent=SymContent.setResultsName("Content",True)
  157.  
  158.     PCBSymbol =pp.Keyword("Symbol")+ LSQ+ Char("Char")+Meas("Delta")+RSQ +LRND+pp.ZeroOrMore(SymContent)+RRND
  159.        
  160.     Via = pp.Keyword("Via")+ LSQ+ Meas("X")+Meas("Y")+Meas("Thickness")+Meas("Clearance")+Meas("Mask")+Meas("Drill")+Str("Name")+QSFlags("SFlags")+RSQ        
  161.     Pin = pp.Keyword("Pin")+ LSQ+ Meas("rX")+Meas("rY")+Meas("Thickness")+Meas("Clearance")+Meas("Mask")+Meas("Drill")+Str("Name")+Str("Number")+QSFlags("SFlags")+RSQ
  162.     Pad = pp.Keyword("Pad")+ LSQ+ Meas("rX1")+Meas("rY1")+Meas("rX2")+Meas("rY2")+Meas("Thickness")+Meas("Clearance")+Meas("Mask")+Str("Name")+Str("Number")+QSFlags("SFlags")+RSQ
  163.  
  164.     ElementArc = pp.Keyword("ElementArc")+ LSQ+ Meas("X")+Meas("Y")+Meas("Width")+Meas("Height")+Digits("StartAngle")+Digits("DeltaAngle")+Meas("Thickness")+RSQ
  165.     ElementLine = pp.Keyword("ElementLine")+ LSQ+ Meas("X1")+Meas("Y1")+Meas("X2")+Meas("Y2")+Meas("Thickness")+RSQ
  166.  
  167.     ElemContent2 = PCBAttribute | Pin| Pad|ElementLine|ElementArc
  168.     ElemContent = G(ElemContent2)
  169.     ElemContent=ElemContent.setResultsName("Content",True)
  170.  
  171.     PCBElement = (pp.Keyword("Element")+ LSQ+ QSFlags("SFlags")+Str("Desc")+Str("Name")+Str("Value")+
  172.                                                                 Meas("MX")+Meas("MY")+Meas("TX")+Meas("TY")+Digits("TDir")+Digits("TScale")+QSFlags("TSFlags")+RSQ  
  173.                                                             +LRND+pp.ZeroOrMore(ElemContent)+RRND )
  174.        
  175.     Rat = pp.Keyword("Rat")+ LSQ+ Meas("X1")+Meas("Y1")+Digits("Group1")+Meas("X2")+Meas("Y2")+Digits("Group2")+QSFlags("SFlags")+RSQ
  176.     Line = pp.Keyword("Line")+ LSQ+  Meas("X1")+Meas("Y1")+Meas("X2")+Meas("Y2")+Meas("Thickness")+Meas("Clearance")+QSFlags("SFlags")+RSQ
  177.     Arc = pp.Keyword("Arc")+ LSQ+ Meas("X")+Meas("Y")+Meas("Width")+Meas("Height")+Meas("Thickness")+Meas("Clearance")+Digits("StartAngle")+Digits("DeltaAngle")+QSFlags("SFlags")+RSQ
  178.     Text = pp.Keyword("Text")+ LSQ+ Meas("X")+Meas("Y")+Digits("Direction")+Meas("Scale")+Str("String")+QSFlags("SFlags")+RSQ
  179.  
  180.     PolyVertex = LSQ+Meas("X")+Meas("Y")+RSQ
  181.     PolyVertex =PolyVertex .setResultsName("Vertex",True)
  182.    
  183.     Hole = pp.Keyword("Hole")+LRND+pp.ZeroOrMore(PolyVertex)+RRND
  184.  
  185.     PolyContent2 = PolyVertex | Hole
  186.     PolyContent = G(PolyContent2)
  187.     PolyContent=PolyContent.setResultsName("Content",True)
  188.    
  189.     Polygon = pp.Keyword("Polygon")+ LRND+QSFlags("SFlags")+RRND+ LRND+pp.ZeroOrMore(PolyContent)+RRND
  190.  
  191.     LayerContent2 = PCBAttribute|Line|Arc|Polygon|Text
  192.     LayerContent = G(LayerContent2)
  193.     LayerContent=LayerContent.setResultsName("Content",True)
  194.    
  195.     Layer = pp.Keyword("Layer")+ LRND+ Digits("LayerNum")+Str("Name")+RRND  +LRND+pp.ZeroOrMore(LayerContent)+RRND
  196.  
  197.     Connect = pp.Keyword("Connect")+LRND+Str("PinPad")+RRND
  198.     Connect =Connect .setResultsName("Connect",True)
  199.        
  200.     Net = pp.Keyword("Net")+ LRND+Str("Name")+Str("Style")+RRND+ LRND+pp.ZeroOrMore(Connect)+RRND
  201.     Net=Net.setResultsName("Net",True)
  202.  
  203.     NetList = pp.Keyword("NetList")+ LRND+ RRND  +LRND+pp.ZeroOrMore(Net)+RRND
  204.  
  205.     TopLevelThing2=Comment|FileVersion|PCB|Grid|PolyArea|Thermal|DRC|Flags|Groups|Styles|Mark|Cursor|PCBSymbol|PCBAttribute|Via|PCBElement|Rat|Layer|NetList
  206.    
  207.     TopLevelThing=G(TopLevelThing2)    
  208.     TopLevelThing=TopLevelThing.setResultsName("Content",True)
  209.  
  210.     FileGrammar=pp.ZeroOrMore( TopLevelThing )
  211.    
  212.     return FileGrammar
  213.  
  214. ##########################
  215.  
  216. FileGrammarParser=BuildBNF(pp)
  217.  
  218. ##########################
  219.  
  220. class PrintContext(object):
  221.     def __init__(self):
  222.         self.printargs={}       # these passed to every print
  223.  
  224. class Writer(object):
  225.  
  226.     class Expression(object):
  227.         def __init__(self):
  228.             self.List=False
  229.             self.Var=None
  230.             self.Suppress=False
  231.            
  232.         def GetCount(self):
  233.             return 0
  234.                    
  235.         def __add__(self,x):
  236.             return Writer.ConcatExpression(self,x)
  237.         def __or__(self,x):
  238.             return Writer.OrExpression(self,x)    
  239.  
  240.         def __call__(self,v):
  241.             return self.setResultsName(v)
  242.         def setResultsName(self,v,list=False):
  243.             x=copy.copy(self)
  244.             x.Var=v
  245.             x.List=list
  246.             return x
  247.        
  248.         def GetVar(self,v):
  249.             if self.Var is None:
  250.                 return None
  251.             else:
  252.                 return v.__getattr__(self.Var)
  253.            
  254.         def DoPrint(self,i,PC):
  255.             raise NotImplementedError
  256.            
  257.     class ConcatExpression(Expression):
  258.         def __init__(self,a,b=None):
  259.             Writer.Expression.__init__(self)
  260.             if b is None:
  261.                 self.Options=a
  262.             elif isinstance(a,Writer.ConcatExpression):
  263.                 self.Options=a.Options+[b]
  264.             else:
  265.                 self.Options=[a,b]
  266.         def GetFirstLiteral(self):
  267.             return self.Options[0].GetFirstLiteral()
  268.  
  269.         def DoPrint(self,x,PC):            
  270.             n=0
  271.             for i in self.Options:                
  272.                 v=i.GetVar(x)
  273.                 if v is None:
  274.                     c=i.GetCount()
  275.                     if c==0:
  276.                         a=i.DoPrint(x,PC)
  277.                     else:
  278.                         a=i.DoPrint(x[n],PC)
  279.                         n+=c
  280.                 else:
  281.                     a=i.DoPrint(v,PC)
  282.                    
  283.     class OrExpression(Expression):
  284.         def __init__(self,a,b=None):
  285.             super(Writer.OrExpression,self).__init__()
  286.             if b is None:
  287.                 self.Options=a
  288.             elif isinstance(a,Writer.OrExpression):
  289.                 self.Options=a.Options+[b]
  290.             else:
  291.                 self.Options=[a,b]
  292.                
  293.         def DoPrint(self,x,PC):
  294.             #print ( [i.GetFirstLiteral() for i in self.Options] )
  295.            
  296.             Done=False
  297.             for i in self.Options:
  298.                 if x[0]==i.GetFirstLiteral():
  299.                     i.DoPrint(x,PC)
  300.                     Done=True
  301.             if not Done:
  302.                 self.Options[0].DoPrint(x,PC)
  303.            
  304.     class Keyword(Expression):
  305.         def __init__(self,s):
  306.             super(Writer.Keyword,self).__init__()
  307.             self.s=s            
  308.         def GetCount(self):
  309.             if self.Suppress:
  310.                 return 0            
  311.             else:
  312.                 return 1            
  313.         def GetFirstLiteral(self):
  314.             return self.s            
  315.         def DoPrint(self,x,PC):
  316.             print (self.s+" ",end="",**PC.printargs)
  317.            
  318.     class Literal(Expression):
  319.         def __init__(self,s):
  320.             super(Writer.Literal,self).__init__()
  321.             self.s=s
  322.         def GetCount(self):
  323.             if self.Suppress:
  324.                 return 0            
  325.             else:
  326.                 return 1
  327.         def GetFirstLiteral(self):
  328.             return self.s            
  329.         def DoPrint(self,x,PC):
  330.             print (self.s+" ",end="",**PC.printargs)
  331.             if self.s==")" or self.s=="]":
  332.                 print ("",**PC.printargs)
  333.  
  334.     class QuotedString(Expression):
  335.         def __init__(self,s):
  336.             super(Writer.QuotedString,self).__init__()
  337.             self.s=s
  338.         def GetCount(self):
  339.             if self.Suppress:
  340.                 return 0            
  341.             else:
  342.                 return 1
  343.         def setParseAction(self,a):
  344.             return self
  345.         def DoPrint(self,x,PC):
  346.             print ('"'+str(x)+'"'+" ",end="",**PC.printargs)
  347.            
  348.     class Regex(Expression):
  349.         def __init__(self,r):
  350.             super(Writer.Regex,self).__init__()
  351.             self.r=r
  352.         def GetCount(self):
  353.             if self.Suppress:
  354.                 return 0            
  355.             else:
  356.                 return 1
  357.         def setParseAction(self,a):
  358.             return self
  359.         def DoPrint(self,x,PC):
  360.             print (str(x)+" ",end="",**PC.printargs)
  361.            
  362.     class Optional(Expression):
  363.         def __init__(self,ex):
  364.             super(Writer.Optional,self).__init__()
  365.             self.ex=ex
  366.         def DoPrint(self,x,PC):
  367.             self.ex.DoPrint (str(x),PC)
  368.            
  369.     class Group(Expression):
  370.         def __init__(self,ex):
  371.             super(Writer.Group,self).__init__()
  372.             self.ex=ex
  373.         def DoPrint(self,x,PC):
  374.             self.ex.DoPrint(x,PC)
  375.            
  376.     class ZeroOrMore(Expression):
  377.         def __init__(self,ex):
  378.             super(Writer.ZeroOrMore,self).__init__()
  379.             self.ex=ex        
  380.         def DoPrint(self,x,PC):
  381.             xx=self.ex.GetVar(x)
  382.             if xx is None:
  383.                 print ("Structure not supported",self.ex.Var)   # all ZeroOrMore must be of something named
  384.                 raise NotImplementedError
  385.             for i in xx:                
  386.                 self.ex.DoPrint(i,PC)
  387.            
  388.     class ROL(Expression):
  389.         def __init__(self):
  390.             self.__class__.__bases__[0].__init__(self)
  391.         def GetCount(self):
  392.             if self.Suppress:
  393.                 return 0            
  394.             else:
  395.                 return 1
  396.         def DoPrint(self,x,PC):
  397.             print(str(x),**PC.printargs)
  398.        
  399.     restOfLine=ROL()
  400.    
  401.     @staticmethod
  402.     def Suppress(S):
  403.         S=copy.copy(S)
  404.         S.Suppress=True
  405.         return S
  406.        
  407.     @staticmethod
  408.     def Combine(S):
  409.         return S
  410.        
  411. Wr=BuildBNF(  Writer )  
  412.  
  413. def PrintTree(x,FileNameOut=None):
  414.     PC=PrintContext
  415.     PC.printargs={}
  416.     if FileNameOut!=None:
  417.         with open(FileNameOut,"w") as f:
  418.             PC.printargs["file"]=f
  419.             Wr.DoPrint(x,PC)
  420.     else:
  421.         Wr.DoPrint(x,PC)
  422.  
  423. ##########################
  424.  
  425. import shapely.geometry.polygon as polygon
  426. import shapely.geometry.point as point
  427.  
  428. def ToShapelyPoly(data):
  429.     verts=[]
  430.     for k in data.Content:
  431.         if k[0]=="Hole":
  432.             pass #FIXME handle this
  433.         else:
  434.             vx=k.X.nm
  435.             vy=k.Y.nm                            
  436.             verts.append( (vx,vy) )            
  437.     return polygon.Polygon(verts)
  438.  
  439. ###########################    
  440.    
  441. def FixThermalsPass(FileNameIn,FileNameOut):
  442.    
  443.     with open(FileNameIn,"r") as f:
  444.         x= FileGrammarParser.parseFile(f,parseAll=True)  
  445.        
  446.         '''
  447.        for each polygon:
  448.            collect info
  449.  
  450.        for each pin/via:    
  451.            for each polygon:
  452.                if intersects:
  453.                    if found and found
  454.                        +this poly layer
  455.                    else
  456.                        -this poly layer
  457.        '''    
  458.        
  459.         print ( "Loaded!",file=sys.stderr)
  460.        
  461.         # Collect all polygons
  462.         Polys=[]
  463.         PolysByLayer={}
  464.        
  465.         for i in x.Content:
  466.             if i[0]=="Layer":
  467.                 layer=i.LayerNum
  468.                 for j in i.Content:
  469.                     if j[0]=="Polygon":
  470.                         p=ToShapelyPoly(j)
  471.                         p.layer=layer
  472.                         p.found=("selected" in j.SFlags)
  473.                         Polys.append(p)
  474.                         PolysByLayer.setdefault(layer,[]).append(p)
  475.                                            
  476.         # Look for polygon/via polygon/pin intersections
  477.         for i in x.Content:
  478.             if i[0]=="Element":
  479.                 mx=i.MX.nm
  480.                 my=i.MY.nm    
  481.                 for j in i.Content:
  482.                     if j[0]=="Pin":                                
  483.                         px=j.rX.nm+mx
  484.                         py=j.rY.nm+my
  485.                         pt=point.Point(px,py)
  486.                         for p in Polys:
  487.                             if p.contains(pt):                                                
  488.                                 if p.found and "selected" in j.SFlags:
  489.                                     j.SFlags.AddThermal(p.layer-1,"!")
  490.                                 else:
  491.                                     j.SFlags.RemoveThermal(p.layer-1)
  492.             elif i[0]=="Via":
  493.                 px=i.X.nm
  494.                 py=i.Y.nm
  495.                 pt=point.Point(px,py)
  496.                 for p in Polys:                
  497.                     if p.contains(pt):                                                
  498.                         if p.found and "selected" in i.SFlags:
  499.                             i.SFlags.AddThermal(p.layer-1,"!")
  500.                         else:
  501.                             i.SFlags.RemoveThermal(p.layer-1)
  502.             elif i[0]=="Layer":
  503.                 layer=i.LayerNum
  504.                 pp=PolysByLayer.get(layer,[])
  505.                 pp=[p for p in pp if p.found]
  506.                 if len(pp)>0:
  507.                     for j in i.Content:
  508.                         if j[0]=="Line":
  509.                             if "selected" in j.SFlags:
  510.                                 x1=j.X1.nm
  511.                                 y1=j.Y1.nm                        
  512.                                 pt1=point.Point(x1,y1)
  513.                                 x2=j.X2.nm
  514.                                 y2=j.Y2.nm                        
  515.                                 pt2=point.Point(x2,y2)
  516.                                
  517.                                 FullyContained=False                  
  518.                                 for p in pp:
  519.                                     c1=p.contains(pt1)
  520.                                     c2=p.contains(pt2)
  521.                                     if c1 and c2:                        
  522.                                         FullyContained=True
  523.                                
  524.                                 if FullyContained:
  525.                                     j.SFlags.Remove("clearline")
  526.                                 else:
  527.                                     j.SFlags.Add("clearline")
  528.                        
  529.         PrintTree(x,FileNameOut=FileNameOut)
  530.    
  531.    
  532. Nets=[ "GND-IO","3.3V-IO","SHIELD-IO" ]
  533.  
  534. FI=sys.argv[1]
  535. FO="a"+FI
  536.  
  537. for n in Nets:
  538.     with open("x.cmd","w") as f:
  539.         print( "Connection(reset)", file=f )
  540.         print( "Unselect(all)", file=f )
  541.         print( "DeleteRats(AllRats)", file=f )
  542.         print( "AddRats(AllRats)", file=f )
  543.         print( "Net(select,%s)"%n, file=f )
  544.         print( "Save()", file=f )
  545.         print( "Quit()", file=f )
  546.  
  547.     cmd="pcb --action-script x.cmd %s" % FI
  548.     print (n,cmd)
  549.     os.system(cmd)
  550.        
  551.     print (FI,"->",FO)
  552.     FixThermalsPass(FI,FO)
  553.    
  554.     FI=FO
  555.     FO="a"+FO
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement