Added support for metric units & Diptrace specific needs

This commit is contained in:
Scott Daniels 2013-03-31 03:46:31 -05:00
parent 38d7510bc9
commit 87e6aab7b5
6 changed files with 262 additions and 53 deletions

View File

@ -22,14 +22,15 @@ import aptable
# Configuration dictionary. Specify floats as strings. Ints can be specified
# as ints or strings.
Config = {
'xspacing': '0.125', # Spacing in horizontal direction
'yspacing': '0.125', # Spacing in vertical direction
'measurementunits': 'inch', # Unit system to use: inch or mm
'xspacing': 0, # Spacing in horizontal direction - default is set in parseConfigFile based on units
'yspacing': 0, # Spacing in vertical direction - ditto
'panelwidth': '12.6', # X-Dimension maximum panel size (Olimex)
'panelheight': '7.8', # Y-Dimension maximum panel size (Olimex)
'cropmarklayers': None, # e.g., *toplayer,*bottomlayer
'cropmarkwidth': '0.01', # Width (inches) of crop lines
'cropmarkwidth': 0, #'0.01', # Width (inches) of crop lines
'cutlinelayers': None, # as for cropmarklayers
'cutlinewidth': '0.01', # Width (inches) of cut lines
'cutlinewidth': 0, #'0.01', # Width (inches) of cut lines
'minimumfeaturesize': 0, # Minimum dimension for selected layers
'toollist': None, # Name of file containing default tool list
'drillclustertolerance': '.002', # Tolerance for clustering drill sizes
@ -255,6 +256,19 @@ def parseConfigFile(fname, Config=Config, Jobs=Jobs):
if Config['cropmarklayers']:
Config['cropmarklayers'] = parseStringList(Config['cropmarklayers'])
# setup default x & y spacing, taking into account metric units
# if (xspacing == 0):
# if (Config['measurementunits'] == 'inch'):
# xspacing = 0.125
# else:
# xspacing = 3
# if (yspacing == 0):
# if (Config['measurementunits'] == 'inch'):
# yspacing = 0.125
# else:
# yspacing = 3
# Process list of minimum feature dimensions
if Config['minimumfeaturesize']:
temp = Config['minimumfeaturesize'].split(",")

View File

@ -88,8 +88,11 @@ the board outline layer for each job.
"""
sys.exit(1)
# changed these two writeGerberHeader files to take metric units (mm) into account:
def writeGerberHeader22degrees(fid):
fid.write( \
if config.Config['measurementunits'] == 'inch':
fid.write( \
"""G75*
G70*
%OFA0B0*%
@ -100,9 +103,21 @@ G70*
5,1,8,0,0,1.08239X$1,22.5*
%
""")
else: # assume mm - also remove eagleware hack for %AMOC8
fid.write( \
"""G75*
G71*
%MOMM*%
%OFA0B0*%
%FSLAX53Y53*%
%IPPOS*%
%LPD*%
""")
def writeGerberHeader0degrees(fid):
fid.write( \
if config.Config['measurementunits'] == 'inch':
fid.write( \
"""G75*
G70*
%OFA0B0*%
@ -112,6 +127,16 @@ G70*
%AMOC8*
5,1,8,0,0,1.08239X$1,0.0*
%
""")
else: # assume mm - also remove eagleware hack for %AMOC8
fid.write( \
"""G75*
G71*
%MOMM*%
%OFA0B0*%
%FSLAX53Y53*%
%IPPOS*%
%LPD*%
""")
writeGerberHeader = writeGerberHeader22degrees
@ -134,6 +159,11 @@ def writeGerberFooter(fid):
fid.write('M02*\n')
def writeExcellonHeader(fid):
if config.Config['measurementunits'] != 'inch': # metric - mm
fid.write( \
"""M48
METRIC,0000.00
""")
fid.write('%\n')
def writeExcellonFooter(fid):
@ -169,39 +199,51 @@ def writeCropMarks(fid, drawing_code, OriginX, OriginY, MaxXExtent, MaxYExtent):
# Draw 125mil lines at each corner, with line edge right up against
# panel border. This means the center of the line is D/2 offset
# from the panel border, where D is the drawing line diameter.
# use 3mm lines for metric
fid.write('%s*\n' % drawing_code) # Choose drawing aperture
offset = config.GAT[drawing_code].dimx/2.0
# should we be using 'cropmarkwidth' from config.py?
if config.Config['measurementunits'] == 'inch':
cropW = 0.125 #inch
else:
cropW = 3 #mm
# Lower-left
x = OriginX + offset
y = OriginY + offset
fid.write('X%07dY%07dD02*\n' % (util.in2gerb(x+0.125), util.in2gerb(y+0.000)))
fid.write('X%07dY%07dD02*\n' % (util.in2gerb(x+cropW), util.in2gerb(y+0.000)))
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+0.000)))
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+0.125)))
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+cropW)))
# Lower-right
x = MaxXExtent - offset
y = OriginY + offset
fid.write('X%07dY%07dD02*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+0.125)))
fid.write('X%07dY%07dD02*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+cropW)))
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+0.000)))
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x-0.125), util.in2gerb(y+0.000)))
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x-cropW), util.in2gerb(y+0.000)))
# Upper-right
x = MaxXExtent - offset
y = MaxYExtent - offset
fid.write('X%07dY%07dD02*\n' % (util.in2gerb(x-0.125), util.in2gerb(y+0.000)))
fid.write('X%07dY%07dD02*\n' % (util.in2gerb(x-cropW), util.in2gerb(y+0.000)))
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+0.000)))
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y-0.125)))
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y-cropW)))
# Upper-left
x = OriginX + offset
y = MaxYExtent - offset
fid.write('X%07dY%07dD02*\n' % (util.in2gerb(x+0.000), util.in2gerb(y-0.125)))
fid.write('X%07dY%07dD02*\n' % (util.in2gerb(x+0.000), util.in2gerb(y-cropW)))
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+0.000)))
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.125), util.in2gerb(y+0.000)))
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+cropW), util.in2gerb(y+0.000)))
def disclaimer():
#return # remove annoying disclaimer
print """
****************************************************
* R E A D C A R E F U L L Y *
@ -264,7 +306,11 @@ def tile_jobs(Jobs):
tile = tilesearch1.tile_search1(L, PX, PY)
if not tile:
raise RuntimeError, 'Panel size %.2f"x%.2f" is too small to hold jobs' % (PX,PY)
# add metric support (1/1000 mm vs. 1/100,000 inch)
if config.Config['measurementunits'] == 'inch':
raise RuntimeError, 'Panel size %.2f"x%.2f" is too small to hold jobs' % (PX,PY)
else:
raise RuntimeError, 'Panel size %.2fmmx%.2fmm is too small to hold jobs' % (PX,PY)
return tile
@ -325,7 +371,11 @@ def merge(opts, args, gui = None):
else:
print
print ' Extents: (%d,%d)-(%d,%d)' % (job.minx,job.miny,job.maxx,job.maxy)
print ' Size: %f" x %f"' % (job.width_in(), job.height_in())
# add metric support (1/1000 mm vs. 1/100,000 inch)
if config.Config['measurementunits'] == 'inch':
print ' Size: %f" x %f"' % (job.width_in(), job.height_in())
else:
print ' Size: %5.3fmm x %5.3fmm' % (job.width_in(), job.height_in())
print
# Trim drill locations and flash data to board extents
@ -343,7 +393,8 @@ def merge(opts, args, gui = None):
# We start origin at (0.1", 0.1") just so we don't get numbers close to 0
# which could trip up Excellon leading-0 elimination.
OriginX = OriginY = 0.1
# I don't want to change the origin. If this a code bug, then it should be fixed (SDD)
OriginX = OriginY = 0 #0.1
# Read the layout file and construct the nested list of jobs. If there
# is no layout file, do auto-layout.
@ -530,7 +581,11 @@ def merge(opts, args, gui = None):
writeGerberHeader(fid)
# Write width-1 aperture to file
AP = aptable.Aperture(aptable.Circle, 'D10', 0.001)
# add metric support
if config.Config['measurementunits'] == 'inch':
AP = aptable.Aperture(aptable.Circle, 'D10', 0.001)
else:
AP = aptable.Aperture(aptable.Circle, 'D10', 0.25) # we'll use 0.25 mm - same as Diptrace
AP.writeDef(fid)
# Choose drawing aperture D10
@ -681,22 +736,38 @@ def merge(opts, args, gui = None):
fid = file(fullname, 'wt')
print '-'*50
print ' Job Size : %f" x %f"' % (MaxXExtent-OriginX, MaxYExtent-OriginY)
print ' Job Area : %.2f sq. in.' % totalarea
# add metric support (1/1000 mm vs. 1/100,000 inch)
if config.Config['measurementunits'] == 'inch':
print ' Job Size : %f" x %f"' % (MaxXExtent-OriginX, MaxYExtent-OriginY)
print ' Job Area : %.2f sq. in.' % totalarea
else:
print ' Job Size : %.2fmm x %.2fmm' % (MaxXExtent-OriginX, MaxYExtent-OriginY)
print ' Job Area : %.0f mm2' % totalarea
print ' Area Usage : %.1f%%' % (jobarea/totalarea*100)
print ' Drill hits : %d' % drillhits
print 'Drill density : %.1f hits/sq.in.' % (drillhits/totalarea)
if config.Config['measurementunits'] == 'inch':
print 'Drill density : %.1f hits/sq.in.' % (drillhits/totalarea)
else:
print 'Drill density : %.2f hits/cm2' % (100*drillhits/totalarea)
print '\nTool List:'
smallestDrill = 999.9
for tool in Tools:
if ToolStats[tool]:
fid.write('%s %.4fin\n' % (tool, config.GlobalToolMap[tool]))
print ' %s %.4f" %5d hits' % (tool, config.GlobalToolMap[tool], ToolStats[tool])
if config.Config['measurementunits'] == 'inch':
fid.write('%s %.4fin\n' % (tool, config.GlobalToolMap[tool]))
print ' %s %.4f" %5d hits' % (tool, config.GlobalToolMap[tool], ToolStats[tool])
else:
fid.write('%s %.4fmm\n' % (tool, config.GlobalToolMap[tool]))
print ' %s %.4fmm %5d hits' % (tool, config.GlobalToolMap[tool], ToolStats[tool])
smallestDrill = min(smallestDrill, config.GlobalToolMap[tool])
fid.close()
print "Smallest Tool: %.4fin" % smallestDrill
if config.Config['measurementunits'] == 'inch':
print "Smallest Tool: %.4fin" % smallestDrill
else:
print "Smallest Tool: %.4fmm" % smallestDrill
print
print 'Output Files :'
@ -706,7 +777,11 @@ def merge(opts, args, gui = None):
if (MaxXExtent-OriginX)>config.Config['panelwidth'] or (MaxYExtent-OriginY)>config.Config['panelheight']:
print '*'*75
print '*'
print '* ERROR: Merged job exceeds panel dimensions of %.1f"x%.1f"' % (config.Config['panelwidth'],config.Config['panelheight'])
# add metric support (1/1000 mm vs. 1/100,000 inch)
if config.Config['measurementunits'] == 'inch':
print '* ERROR: Merged job exceeds panel dimensions of %.1f"x%.1f"' % (config.Config['panelwidth'],config.Config['panelheight'])
else:
print '* ERROR: Merged job exceeds panel dimensions of %.1fmmx%.1fmm' % (config.Config['panelwidth'],config.Config['panelheight'])
print '*'
print '*'*75
sys.exit(1)

View File

@ -36,6 +36,11 @@ import util
# D02 -- move with exposure off
# D03 -- flash aperture
# TODO:
#
# Need to add error checking for metric/imperial units matching those of the files input
# Check fabdrawing.py to see if writeDrillHits is scaling properly (the only place it is used)
# Patterns for Gerber RS274X file interpretation
apdef_pat = re.compile(r'^%AD(D\d+)([^*$]+)\*%$') # Aperture definition
apmdef_pat = re.compile(r'^%AM([^*$]+)\*$') # Aperture macro definition
@ -180,12 +185,21 @@ class Job:
self.ExcellonDecimals = 0 # 0 means global value prevails
def width_in(self):
"Return width in INCHES"
return float(self.maxx-self.minx)*0.00001
# add metric support (1/1000 mm vs. 1/100,000 inch)
if config.Config['measurementunits'] == 'inch':
"Return width in INCHES"
return float(self.maxx-self.minx)*0.00001
else:
return float(self.maxx-self.minx)*0.001
def height_in(self):
"Return height in INCHES"
return float(self.maxy-self.miny)*0.00001
# add metric support (1/1000 mm vs. 1/100,000 inch)
if config.Config['measurementunits'] == 'inch':
"Return height in INCHES"
return float(self.maxy-self.miny)*0.00001
else:
return float(self.maxy-self.miny)*0.001
def jobarea(self):
return self.width_in()*self.height_in()
@ -339,6 +353,21 @@ class Job:
if line[:7]=='%AMOC8*':
continue
# DipTrace specific fixes, but could be emitted by any CAD program. They are Standard Gerber RS-274X
# a hack to fix lack of recognition for metric direction from DipTrace - %MOMM*%
if (line[:7] == '%MOMM*%'):
if (config.Config['measurementunits'] == 'inch'):
raise RuntimeError, "File %s units do match config file" % fullname
else:
#print "ignoring metric directive: " + line
continue # ignore it so func doesn't choke on it
if line[:3] == '%SF': # scale factor - we will ignore it
print 'Scale factor parameter ignored: ' + line
continue
# end basic diptrace fixes
# See if this is an aperture macro definition, and if so, map it.
M = amacro.parseApertureMacro(line,fid)
if M:
@ -384,12 +413,24 @@ class Job:
if item[0]=='N': # Maximum digits for N* commands...ignore it
continue
if item[0]=='X': # M.N specification for X-axis.
fracpart = int(item[2])
x_div = 10.0**(5-fracpart)
if item[0]=='Y': # M.N specification for Y-axis.
fracpart = int(item[2])
y_div = 10.0**(5-fracpart)
# allow for metric - scale to 1/1000 mm
if config.Config['measurementunits'] == 'inch':
if item[0]=='X': # M.N specification for X-axis.
fracpart = int(item[2])
x_div = 10.0**(5-fracpart)
if item[0]=='Y': # M.N specification for Y-axis.
fracpart = int(item[2])
y_div = 10.0**(5-fracpart)
else:
if item[0]=='X': # M.N specification for X-axis.
fracpart = int(item[2])
x_div = 10.0**(3-fracpart)
#print "x_div= %5.3f." % x_div
if item[0]=='Y': # M.N specification for Y-axis.
fracpart = int(item[2])
y_div = 10.0**(3-fracpart)
#print "y_div= %5.3f." % y_div
continue
# Parse and interpret G-codes
@ -400,7 +441,8 @@ class Job:
# Determine if this is a G-Code that should be ignored because it has no effect
# (e.g., G70 specifies "inches" which is already in effect).
if gcode in [54, 70, 90]:
# added 71 - specify mm (metric)
if gcode in [54, 70, 90, 71]:
continue
# Determine if this is a G-Code that we have to emit because it matters.
@ -427,6 +469,15 @@ class Job:
if match:
currtool = match.group(1)
# Diptrace hack
# There is a D2* command in board outlines. I believe this should be D02. Let's change it then when it occurs:
if (currtool == 'D1'):
currtool = 'D01'
if (currtool == 'D2'):
currtool = 'D02'
if (currtool == 'D3'):
currtool = 'D03'
# Protel likes to issue random D01, D02, and D03 commands instead of aperture
# codes. We can ignore D01 because it simply means to move to the current location
# while drawing. Well, that's drawing a point. We can ignore D02 because it means
@ -597,6 +648,18 @@ class Job:
# Get rid of CR characters
line = string.replace(line, '\x0D', '')
# add support for DipTrace
if line[:6]=='METRIC':
if (config.Config['measurementunits'] == 'inch'):
raise RuntimeError, "File %s units do match config file" % fullname
else:
#print "ignoring METRIC directive: " + line
continue # ignore it so func doesn't choke on it
if line[:3] == 'T00': # a tidying up that we can ignore
continue
# end metric/diptrace support
# Protel likes to embed comment lines beginning with ';'
if line[0]==';':
continue
@ -704,9 +767,15 @@ class Job:
# Maybe we don't have this layer
if not self.hasLayer(layername): return
# First convert given inches to 2.5 co-ordinates
X = int(round(Xoff/0.00001))
Y = int(round(Yoff/0.00001))
# add metric support (1/1000 mm vs. 1/100,000 inch)
if config.Config['measurementunits'] == 'inch':
# First convert given inches to 2.5 co-ordinates
X = int(round(Xoff/0.00001))
Y = int(round(Yoff/0.00001))
else:
# First convert given mm to 5.3 co-ordinates
X = int(round(Xoff/0.001))
Y = int(round(Yoff/0.001))
# Now calculate displacement for each position so that we end up at specified origin
DX = X - self.minx
@ -749,14 +818,20 @@ class Job:
# and our internal Excellon representation is 2.4 as of GerbMerge
# version 0.91. We use X,Y to calculate DX,DY in 2.4 units (i.e., with a
# resolution of 0.0001".
X = int(round(Xoff/0.00001)) # First work in 2.5 format to match Gerber
Y = int(round(Yoff/0.00001))
# add metric support (1/1000 mm vs. 1/100,000 inch)
if config.Config['measurementunits'] == 'inch':
X = int(round(Xoff/0.00001)) # First work in 2.5 format to match Gerber
Y = int(round(Yoff/0.00001))
else:
X = int(round(Xoff/0.001)) # First work in 5.3 format to match Gerber
Y = int(round(Yoff/0.001))
# Now calculate displacement for each position so that we end up at specified origin
DX = X - self.minx
DY = Y - self.miny
# Now round down to 2.4 format
# this scaling seems to work for either unit system
DX = int(round(DX/10.0))
DY = int(round(DY/10.0))
@ -778,9 +853,15 @@ class Job:
"""Write a drill hit pattern. diameter is tool diameter in inches, while toolNum is
an integer index into strokes.DrillStrokeList"""
# First convert given inches to 2.5 co-ordinates
X = int(round(Xoff/0.00001))
Y = int(round(Yoff/0.00001))
# add metric support (1/1000 mm vs. 1/100,000 inch)
if config.Config['measurementunits'] == 'inch':
# First convert given inches to 2.5 co-ordinates
X = int(round(Xoff/0.00001))
Y = int(round(Yoff/0.00001))
else:
# First convert given inches to 5.3 co-ordinates
X = int(round(Xoff/0.001))
Y = int(round(Yoff/0.001))
# Now calculate displacement for each position so that we end up at specified origin
DX = X - self.minx
@ -795,6 +876,8 @@ class Job:
if self.xcommands.has_key(ltool):
for cmd in self.xcommands[ltool]:
x, y = cmd
# add metric support (1/1000 mm vs. 1/100,000 inch)
# TODO - verify metric scaling is correct???
makestroke.drawDrillHit(fid, 10*x+DX, 10*y+DY, toolNum)
def aperturesAndMacros(self, layername):
@ -881,9 +964,14 @@ class Job:
newX, newY = geometry.rectCenter(newRect)
# We arbitrarily remove all flashes that lead to rectangles
# with a width or length less than 1 mil (10 Gerber units).
# with a width or length less than 1 mil (10 Gerber units). - sdd s.b. 0.1mil???
# Should we make this configurable?
if min(newRectWidth, newRectHeight) >= 10:
# add metric support (1/1000 mm vs. 1/100,000 inch)
# if config.Config['measurementunits'] == 'inch':
# minFlash = 10;
# else
# minFlash =
if min(newRectWidth, newRectHeight) >= 10: # sdd - change for metric case at some point
# Construct an Aperture that is a Rectangle of dimensions (newRectWidth,newRectHeight)
newAP = aptable.Aperture(aptable.Rectangle, 'D??', \
util.gerb2in(newRectWidth), util.gerb2in(newRectHeight))
@ -1006,7 +1094,12 @@ class Job:
keys = self.xcommands.keys()
for toolname in keys:
# Remember Excellon is 2.4 format while Gerber data is 2.5 format
validList = [(x,y) for x,y in self.xcommands[toolname] if self.inBorders(10*x,10*y)]
# add metric support (1/1000 mm vs. 1/100,000 inch)
# the normal metric scale factor isn't working right, so we'll leave it alone!!!!?
if config.Config['measurementunits'] == 'inch':
validList = [(x,y) for x,y in self.xcommands[toolname] if self.inBorders(10*x,10*y)]
else:
validList = [(x,y) for x,y in self.xcommands[toolname] if self.inBorders(0.1*x,0.1*y)]
if validList:
self.xcommands[toolname] = validList
@ -1271,12 +1364,16 @@ def rotateJob(job, degrees = 90, firstpass = True):
J.xcommands[tool] = []
for x,y in job.xcommands[tool]:
# add metric support (1/1000 mm vs. 1/100,000 inch)
# NOTE: There don't appear to be any need for a change. The usual x10 factor seems to apply
newx = -(10*y - job.miny) + job.minx + offset
newy = (10*x - job.minx) + job.miny
newx = int(round(newx/10.0))
newy = int(round(newy/10.0))
J.xcommands[tool].append((newx,newy))
# Rotate some more if required

View File

@ -39,8 +39,15 @@ def printTilingStats():
percent = 100.0*_Permutations/_PossiblePermutations
print "\r %5.2f%% complete / %ld/%ld Perm/Place / Smallest area: %.1f sq. in. / Best utilization: %.1f%%" % \
# add metric support (1/1000 mm vs. 1/100,000 inch)
if config.Config['measurementunits'] == 'inch':
print "\r %5.2f%% complete / %ld/%ld Perm/Place / Smallest area: %.1f sq. in. / Best utilization: %.1f%%" % \
(percent, _Permutations, _Placements, area, utilization),
else:
print "\r %5.2f%% complete / %ld/%ld Perm/Place / Smallest area: %.1f sq. mm / Best utilization: %.1f%%" % \
(percent, _Permutations, _Placements, area, utilization),
if gerbmerge.GUI is not None:
sys.stdout.flush()

View File

@ -36,7 +36,12 @@ def printTilingStats():
area = 999999.0
utilization = 0.0
print "\r %ld placements / Smallest area: %.1f sq. in. / Best utilization: %.1f%%" % \
# add metric support (1/1000 mm vs. 1/100,000 inch)
if config.Config['measurementunits'] == 'inch':
print "\r %ld placements / Smallest area: %.1f sq. in. / Best utilization: %.1f%%" % \
(_Placements, area, utilization),
else:
print "\r %ld placements / Smallest area: %.1f sq. mm / Best utilization: %.0f%%" % \
(_Placements, area, utilization),
if gerbmerge.GUI is not None:

View File

@ -11,10 +11,21 @@ Rugged Circuits LLC
http://ruggedcircuits.com/gerbmerge
"""
import config
def in2gerb(value):
"""Convert inches to 2.5 Gerber units"""
return int(round(value*1e5))
# add metric support (1/1000 mm vs. 1/100,000 inch)
if config.Config['measurementunits'] == 'inch':
"""Convert inches to 2.5 Gerber units"""
return int(round(value*1e5))
else: #convert mm to 5.3 Gerber units
return int(round(value*1e3))
def gerb2in(value):
"""Convert 2.5 Gerber units to inches"""
return float(value)*1e-5
# add metric support (1/1000 mm vs. 1/100,000 inch)
if config.Config['measurementunits'] == 'inch':
"""Convert 2.5 Gerber units to inches"""
return float(value)*1e-5
else: #convert 5.3 Gerber units to mm
return float(value)*1e-3