[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

Re: gEDA-user: Importing board outline from .emn file



Hi Folks,

I have just finished writing a Python script to import mechanical data
(from ProEng) into pcb and thought I would offer it up to the community
if anyone has a similar need.

The script is attached and it is used in the following way:-

> python importEmn.py emnFile pcbFile_in pcbFile_out

where:-
emnFile is the emn file being imported with the mechanical data. It is
structured following the IDF3.0 standard referenced below
www.aertia.com/docs/priware/IDF_V30_Spec.pdf

pcbFile_in is the original pcb file that will not be modified

pcbFile-out is a copy of pcbFile_in with the mechanical information
added to the appropriate layers (if they exist)

At this stage it supports the import of outline, keepouts and drill data
but this could be expanded if there is any interest.

Cheers, Neil

On Wed, 2009-02-04 at 23:08 -0500, Neil Webster wrote: 
> Hi Folks,
> 
> Does anyone know if there is a way to import a board outline defined in
> an emn file into PCB? The emn file is produced by ProEng 3-D CAD tool
> and it seems quite simply structured. I have attached it below ...
> 
> .HEADER
> BOARD_FILE 3.0 "Pro/ENGINEER  TM  Wildfire 3.0  (c" 2009/02/04.13:30:43
> 3
> PCB-DB                                  MM        
> .END_HEADER
> .BOARD_OUTLINE  MCAD
> 1.20000             
>          0             0.00000             0.00000             0.00000
>          0            60.00000             0.00000             0.00000
>          0            60.00000            30.00000             0.00000
>          0             0.00000            30.00000             0.00000
>          0             0.00000             0.00000             0.00000
> .END_BOARD_OUTLINE
> .DRILLED_HOLES
> 3.50000        21.50000       16.00000       NPTH    BOARD PIN
> MCAD   
> .END_DRILLED_HOLES
> .PLACE_KEEPOUT      UNOWNED
> TOP 0.00000        
>          0             7.83200            30.00000             0.00000
>          0             7.83200            27.99400             0.00000
>          0            10.83200            27.99400             0.00000
>          0            10.83200            30.00000             0.00000
>          0             7.83200            30.00000             0.00000
> .END_PLACE_KEEPOUT
> .PLACE_KEEPOUT      UNOWNED
> TOP 0.00000        
>          0            49.14500            30.00000             0.00000
>          0            49.14500            27.99400             0.00000
>          0            52.14500            27.99400             0.00000
>          0            52.14500            30.00000             0.00000
>          0            49.14500            30.00000             0.00000
> .END_PLACE_KEEPOUT
> .PLACE_KEEPOUT      UNOWNED
> TOP 0.00000        
>          0            10.84300             0.00000             0.00000
>          0             7.84300             0.00000             0.00000
>          0             7.84300             2.00600             0.00000
>          0            10.84300             2.00600             0.00000
>          0            10.84300             0.00000             0.00000
> .END_PLACE_KEEPOUT
> .PLACE_KEEPOUT      UNOWNED
> TOP 0.00000        
>          0            52.12000             0.00000             0.00000
>          0            49.12000             0.00000             0.00000
>          0            49.12000             2.00600             0.00000
>          0            52.12000             2.00600             0.00000
>          0            52.12000             0.00000             0.00000
> .END_PLACE_KEEPOUT
> .PLACE_KEEPOUT      UNOWNED
> TOP 0.00000        
>          0             0.00000            18.87600             0.00000
>          0             0.00000            15.87600             0.00000
>          0             2.00600            15.87600             0.00000
>          0             2.00600            18.87600             0.00000
>          0             0.00000            18.87600             0.00000
> .END_PLACE_KEEPOUT
> .PLACE_KEEPOUT      UNOWNED
> TOP 0.00000        
>          0            57.99400            18.88100             0.00000
>          0            60.00000            18.88100             0.00000
>          0            60.00000            15.88100             0.00000
>          0            57.99400            15.88100             0.00000
>          0            57.99400            18.88100             0.00000
> .END_PLACE_KEEPOUT
> 
> 
> 
> 
> 
> 
> _______________________________________________
> geda-user mailing list
> geda-user@xxxxxxxxxxxxxx
> http://www.seul.org/cgi-bin/mailman/listinfo/geda-user
> 
> 
#author: Neil Webster
#data: 11 February 2009
#!/usr/bin/env python

#this script imports an emn file into a pcb file
#to import outline information, the pcb file must already have an "outline" layer defined
#to import keepout information, the pcb file must already have a "keepout" layer defined

#usage python importEmn.py emnFile pcbFile_in pcbFile_out
# emnFile is the emn file being imported with the mechanical data
# pcbFile_in is the original pcb file that will not be modified
# pcbFile-out is a copy of pcbFile_in with the machanical infomation added

#the emnfile format is described in Intermediate Data Format (IDF3.0) standard

import sys
#set up constants to convert to PCB units of 1/100,000 inch
#IDF3.0 files are defined in "MM" or "THOU"
mmToPcb = 0.0393700787 * 100000
thouToPcb = 100

class Point:
  #this is an IDF3.0 point as defined in Record3 of section 3.2
  def __init__(self, recordString):
    parts = recordString.split()
    self.loopLabel = int(parts[0])
    self.xCoordinateOfPoint = float(parts[1])
    self.yCoordinateOfPoint = float(parts[2])
    self.includeAngle = float(parts[3])

class Section:
  #the Section class holds the records and associated fields of an IDF3.0 section
  #each section has its own defined fields and the constructor creates the correct fields
  #dependant on the type of section being defined
  def __init__(self, sectionText):
    #the contructor is passed a section of text extracted from the file bounded by begin and end tags
    if "HEADER" in sectionText[0]:
      #see section 3.1 of IDF3.0
      s1 = sectionText[0].split()
      self.type = s1[0].lstrip('.').upper()
      s2 = splitRecord(sectionText[1])
      self.fileType = s2[0]
      self.idfVersionNumber = float(s2[1])
      self.sourceSystemId = s2[2]
      self.date = s2[3]
      self.boardFileVersion = int(s2[4])
      s3 = sectionText[2].split()
      self.boardName = s3[0]
      self.unitsDefinition = s3[1]
    elif "BOARD_OUTLINE" in sectionText[0]:
      #see section 3.2 of IDF3.0
      s1 = sectionText[0].split()
      self.type = s1[0].lstrip('.').upper()
      self.outlineOwner = s1[1]
      self.thickness = float(sectionText[1])
      self.outlinePath = []
      pathText = sectionText[2:-1]
      for i in pathText:
        self.outlinePath.append(Point(i))
    elif "PLACE_KEEPOUT" in sectionText[0]:
      #see section 3.8 of IDF3.0
      s1 = sectionText[0].split()
      self.type = s1[0].lstrip('.').upper()
      self.keepoutOwner = s1[1]
      s2 = sectionText[1].split()
      self.boardSide = s2[0]
      self.keepoutHeight = float(s2[1])
      self.keepoutPath = []
      pathText = sectionText[2:-1]
      for i in pathText:
        self.keepoutPath.append(Point(i))
    elif "DRILLED_HOLES" in sectionText[0]:
      #see section3.10 of IDF3.0
      s1 = sectionText[0].split()
      self.type = s1[0].lstrip('.').upper()
      s2 = sectionText[1].split()
      self.holeDiameter = float(s2[0])
      self.xCoordinateOfHole = float(s2[1])
      self.yCoordinateOfHole = float(s2[2])
      self.platingStyle = s2[3]
      self.associatedPart = s2[4]
      self.holeType = s2[5]
      self.holeOwner = s2[6]
    else: 
      print "section not defined"

def splitRecord(recordString):
  #find any literal strings embedded within the recordString
  interim = recordString.split('"')
  if len(interim) == 1: #there are no literal strings.
    return recordString.split() #regular split is fine
  if len(interim) == 3: #there is one literal string and it must be in the middle
    #split the first and last elements and add the middle (literal string) intact
    return interim[0].split() + [interim[1]] + interim[2].split()

#read in the emn file which is the first argument
f = open(sys.argv[1])
emn = f.readlines()
f.close()

#remove any comment lines 
emn = [l for l in emn if not l.startswith("#")]
#find the keyword lines
keywords = [l for l in emn if l.startswith(".")]
#break the keywords into pairs in separate lists, even in beginKeywrods; odd in endKeywords
beginKeywords = keywords[::2]
endKeywords = keywords[1::2]
#create a list to hold an object oriented representation of each of the emn file sections
sections = []
while len(beginKeywords)>0:
  b = beginKeywords[0]
  e = endKeywords[0]
  #extract the text block between the begin and end keywords
  sectionText = emn[emn.index(b):emn.index(e)+1]
  #add a new section to the end of the list
  sections.append(Section(sectionText))
  #remove the processed elements
  beginKeywords.remove(b)
  endKeywords.remove(e)
  for i in sectionText:
    emn.remove(i)

def createPcbLine(point1,point2,units):
  #this takes 2x IDF3.0 points object and returns a pcb Line as a string
  #initially just supports line and not arc or cutouts
  
  #make tuples of the 2 points
  p1 = (point1.xCoordinateOfPoint, point1.yCoordinateOfPoint)
  p2 = (point2.xCoordinateOfPoint, point2.yCoordinateOfPoint)
  if units == "MM": 
    conv = mmToPcb
  elif units == "THOU":
    conv = thouToPcb
  #convert the tuples to the pcb units  
  p1 = [int(x*conv) for x in p1]
  p2 = [int(x*conv) for x in p2]
  #dealing with straight line segments therefore loopLabel=0 and includeAngle=0 but these are not checked yet
  #note that line thickness is set to 1000 and clearance to 2000, flags set to ""
  return "\tLine[" + str(p1[0]) + " " + str(p1[1]) + " " + str(p2[0]) + " " + str(p2[1]) + " 1000 2000 \"\"]\n"

#read in the pcb file which is the second argument
f = open(sys.argv[2])
pcb = f.readlines()
f.close()  
#find the Layer definitions
pcbLayers = [l for l in pcb if l.startswith("Layer")]
#find the exact index of the outline & keepout layers if they exist
outlineLayerIndex = 0
keepoutLayerIndex = 0
for l in pcbLayers:
  if "outline" in l:
    outlineLayerIndex = pcb.index(l)
  if "keepout" in l:
    keepoutLayerIndex = pcb.index(l)
#extract the units from the HEADER section, should only be one header but use first item in list
units = [s.unitsDefinition for s in sections if s.type == "HEADER"][0]
#if we have an outline layer then add the outline data
if outlineLayerIndex != 0:
  #get the outline path from the BOARD_OUTLINE section. There should only be one but take the first anyway just in case
  olp = [s.outlinePath for s in sections if s.type == "BOARD_OUTLINE"][0]
  #IDF3.0 works with points and pcb works with lines i.e. a pair of adjacent point
  #go through all points in the path except the last
  for lp1 in olp[0:-1]:
    #the second point of a pair is the next one in the list
    lp2 = olp[olp.index(lp1)+1]
    #insert after the Layer line and the next line which hold a (, hence increment of 2
    pcb.insert(outlineLayerIndex+2,createPcbLine(lp1,lp2,units))
#if we have an keepout layer then add the keepout data
if keepoutLayerIndex != 0:
  #pull out a list of keepout paths
  allKeepouts = [s.keepoutPath for s in sections if s.type=="PLACE_KEEPOUT"]
  for singleKeepout in allKeepouts: #for each path in the list
    for lp1 in singleKeepout[0:-1]: #a single keepout is a closed collection of points
      lp2 = singleKeepout[singleKeepout.index(lp1)+1]
      #insert after the Layer line and the next line which hold a (, hence increment of 2
      pcb.insert(keepoutLayerIndex+2,createPcbLine(lp1,lp2,units))
#add drilled holes
dh = [s for s in sections if s.type == "DRILLED_HOLES"]
if units == "MM": 
  conv = mmToPcb
elif units == "THOU":
  conv = thouToPcb
#find where to insert the drill data in the pcb file, it should be just before the NetList section
drillDataIndex = pcb.index("NetList()\n")
for hole in dh:
  x = int(conv*(hole.xCoordinateOfHole))
  y = int(conv*(hole.yCoordinateOfHole))
  diam = int(conv*(hole.holeDiameter))
  pcb.insert(drillDataIndex,"Via[" + str(x) + " " + str(y) + " " + str(diam) + " 2000 0 " + str(diam) + """ "" "hole"]"""+"\n") 
  
#write the newly created file
f = open(sys.argv[3],"w")
f.writelines(pcb)
f.close()

_______________________________________________
geda-user mailing list
geda-user@xxxxxxxxxxxxxx
http://www.seul.org/cgi-bin/mailman/listinfo/geda-user