gerbmerge/gerbmerge/scoring.py

302 lines
9.9 KiB
Python

#!/usr/bin/env python
"""This file handles the writing of the scoring lines Gerber file
--------------------------------------------------------------------
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 config
import util
import makestroke
# Add a horizontal line if its within the extents of the panel. Also, trim
# start and/or end points to the extents.
def addHorizontalLine(Lines, x1, x2, y, extents):
assert (x1 < x2)
# For a horizontal line, y must be above extents[1] and below extents[3].
if extents[1] < y < extents[3]:
# Now trim endpoints to be greater than extents[0] and below extents[2]
line = (max(extents[0], x1), y, min(extents[2], x2), y)
Lines.append(line)
# Add a vertical line if its within the extents of the panel. Also, trim
# start and/or end points to the extents.
def addVerticalLine(Lines, x, y1, y2, extents):
assert (y1 < y2)
# For a vertical line, x must be above extents[0] and below extents[2].
if extents[0] < x < extents[2]:
# Now trim endpoints to be greater than extents[1] and below extents[3]
line = (x, max(extents[1], y1), x, min(extents[3], y2))
Lines.append(line)
def isHorizontal(line):
return line[1]==line[3]
def isVertical(line):
return line[0]==line[2]
def clusterOrdinates(values):
"""Create a list of tuples where each tuple is a variable-length list of items
from 'values' that are all within 2 mils of each other."""
# First, make sure the values are sorted. Then, take the first one and go along
# the list clustering as many as possible.
values.sort()
currCluster = None
L = []
for val in values:
if currCluster is None:
currCluster = (val,)
else:
if (val - currCluster[0]) <= 0.002:
currCluster = currCluster + (val,)
else:
L.append(currCluster)
currCluster = (val,)
if currCluster is not None:
L.append(currCluster)
return L
def mergeHLines(Lines):
"""Lines is a list of 4-tuples (lines) that have nearly the same Y ordinate and are to be
optimized by combining overlapping lines."""
# First, make sure lines are sorted by starting X ordinate and that all lines
# proceed to the right.
Lines.sort()
for line in Lines:
assert line[0] < line[2]
# Obtain the average value of the Y ordinate and use that as the Y ordinate for
# all lines.
yavg = 0.0
for line in Lines:
yavg += line[1]
yavg /= len(Lines)
NewLines = []
# Now proceed to pick off one line at a time and try to merge it with
# the next one in sequence.
currLine = None
for line in Lines:
if currLine is None:
currLine = line
else:
# If the line to examine starts to the left of (within 0.002") the end
# of the current line, extend the current line.
if line[0] <= currLine[2]+0.002:
currLine = (currLine[0], yavg, max(line[2],currLine[2]), yavg)
else:
NewLines.append(currLine)
currLine = line
NewLines.append(currLine)
return NewLines
def sortByY(A,B):
"Helper function to sort two lines (4-tuples) by their starting Y ordinate"
return cmp(A[1], B[1])
def mergeVLines(Lines):
"""Lines is a list of 4-tuples (lines) that have nearly the same X ordinate and are to be
optimized by combining overlapping lines."""
# First, make sure lines are sorted by starting Y ordinate and that all lines
# proceed up.
Lines.sort(sortByY)
for line in Lines:
assert line[1] < line[3]
# Obtain the average value of the X ordinate and use that as the X ordinate for
# all lines.
xavg = 0.0
for line in Lines:
xavg += line[0]
xavg /= len(Lines)
NewLines = []
# Now proceed to pick off one line at a time and try to merge it with
# the next one in sequence.
currLine = None
for line in Lines:
if currLine is None:
currLine = line
else:
# If the line to examine starts below (within 0.002") the end
# of the current line, extend the current line.
if line[1] <= currLine[3]+0.002:
currLine = (xavg, currLine[1], xavg, max(line[3],currLine[3]))
else:
NewLines.append(currLine)
currLine = line
NewLines.append(currLine)
return NewLines
def mergeLines(Lines):
# All lines extend up (vertical) and to the right (horizontal). First, do
# simple merges. Sort all lines, which will order the lines with starting
# points in increasing X order (i.e., to the right).
Lines.sort()
# Now sort the lines into horizontal lines and vertical lines. For each
# ordinate, group all lines by that ordinate in a dictionary. Thus, all
# horizontal lines will be grouped together by Y ordinate, and all
# vertical lines will be grouped together by X ordinate.
HLines = {}
VLines = {}
for line in Lines:
if isHorizontal(line):
try:
HLines[line[1]].append(line)
except KeyError:
HLines[line[1]] = [line]
else:
try:
VLines[line[0]].append(line)
except KeyError:
VLines[line[0]] = [line]
# I don't think the next two blocks of code are necessary (merging lines
# that are at exactly the same ordinate) since the last two blocks of
# code do the same thing more generically by merging lines at close-enough
# ordinates.
# Extend horizontal lines
NewHLines = {}
for yval,lines in list(HLines.items()):
# yval is the Y ordinate of this group of lines. lines is the set of all
# lines with this Y ordinate.
NewHLines[yval] = []
# Try to extend the first element of this list, which will be the leftmost.
xline = lines[0]
for line in lines[1:]:
# If this line's left edge is within 2 mil of the right edge of the line
# we're currently trying to grow, then grow it.
if abs(line[0] - xline[2]) <= 0.002: # Arbitrary 2mil?
# Extend...
xline = (xline[0], xline[1], line[2], xline[1])
else:
# ...otherwise, append the currently-extended line and make this
# line the new one we try to extend.
NewHLines[yval].append(xline)
xline = line
NewHLines[yval].append(xline)
# Extend vertical lines
NewVLines = {}
for xval,lines in list(VLines.items()):
# xval is the X ordinate of this group of lines. lines is the set of all
# lines with this X ordinate.
NewVLines[xval] = []
# Try to extend the first element of this list, which will be the bottom-most.
xline = lines[0]
for line in lines[1:]:
# If this line's bottom edge is within 2 mil of the top edge of the line
# we're currently trying to grow, then grow it.
if abs(line[1] - xline[3]) <= 0.002: # Arbitrary 2mil?
# Extend...
xline = (xline[0], xline[1], xline[0], line[3])
else:
# ...otherwise, append the currently-extended line and make this
# line the new one we try to extend.
NewVLines[xval].append(xline)
xline = line
NewVLines[xval].append(xline)
HLines = NewHLines
VLines = NewVLines
NewHLines = []
NewVLines = []
# Now combine lines that have their endpoints either very near each other
# or within each other. We will have to sort all horizontal lines by their
# Y ordinates and group them according to Y ordinates that are close enough
# to each other.
yvals = list(HLines.keys())
clusters = clusterOrdinates(yvals) # A list of clustered tuples containing yvals
for cluster in clusters:
clusterLines = []
for yval in cluster:
clusterLines.extend(HLines[yval])
# clusterLines is now a list of lines (4-tuples) that all have nearly the same
# Y ordinate. Merge them together.
NewHLines.extend(mergeHLines(clusterLines))
xvals = list(VLines.keys())
clusters = clusterOrdinates(xvals)
for cluster in clusters:
clusterLines = []
for xval in cluster:
clusterLines.extend(VLines[xval])
# clusterLines is now a list of lines (4-tuples) that all have nearly the same
# X ordinate. Merge them together.
NewVLines.extend(mergeVLines(clusterLines))
Lines = NewHLines + NewVLines
return Lines
# Main entry point. Gerber file has already been opened, header written
# out, 1mil tool selected.
def writeScoring(fid, Place, OriginX, OriginY, MaxXExtent, MaxYExtent):
# For each job, write out 4 score lines, above, to the right, below, and
# to the left. After we collect all potential scoring lines, we worry
# about merging, etc.
dx = config.Config['xspacing']/2.0
dy = config.Config['yspacing']/2.0
extents = (OriginX, OriginY, MaxXExtent, MaxYExtent)
Lines = []
for layout in Place.jobs:
x = layout.x - dx
y = layout.y - dy
X = layout.x + layout.width_in() + dx
Y = layout.y + layout.height_in() + dy
# Just so we don't get 3.75000000004 and 3.75000000009, we round to
# 2.5 limits.
x,y,X,Y = [round(val,5) for val in [x,y,X,Y]]
if 0: # Scoring lines go all the way across the panel now
addHorizontalLine(Lines, x, X, Y, extents) # above job
addVerticalLine(Lines, X, y, Y, extents) # to the right of job
addHorizontalLine(Lines, x, X, y, extents) # below job
addVerticalLine(Lines, x, y, Y, extents) # to the left of job
else:
addHorizontalLine(Lines, OriginX, MaxXExtent, Y, extents) # above job
addVerticalLine(Lines, X, OriginY, MaxYExtent, extents) # to the right of job
addHorizontalLine(Lines, OriginX, MaxXExtent, y, extents) # below job
addVerticalLine(Lines, x, OriginY, MaxYExtent, extents) # to the left of job
# Combine disparate lines into single lines
Lines = mergeLines(Lines)
#for line in Lines:
# print [round(x,3) for x in line]
# Write 'em out
for line in Lines:
makestroke.drawPolyline(fid, [(util.in2gerb(line[0]),util.in2gerb(line[1])), \
(util.in2gerb(line[2]),util.in2gerb(line[3]))], 0, 0)
# vim: expandtab ts=2 sw=2 ai syntax=python